把pdf字体替换成微软雅黑

This commit is contained in:
chenqiang 2025-09-08 11:55:46 +08:00
parent 836b38c4b8
commit be857ff36a
15 changed files with 108 additions and 214 deletions

4
.gitignore vendored
View File

@ -25,4 +25,6 @@ pnpm-debug.log*
#Electron-builder output #Electron-builder output
/dist_electron /dist_electron
/high_version /high_version
/output

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -49,19 +49,12 @@ function getFontPath() {
// 现在使用getFontPath()函数来获取字体路径 // 现在使用getFontPath()函数来获取字体路径
const FONT_PATH = getFontPath(); const FONT_PATH = getFontPath();
// 优先使用SourceHanSansSC字体 // 统一使用微软雅黑字体
const primaryFontPath = path.join(FONT_PATH, 'SourceHanSansSC-Regular.otf'); const msyhFontPath = path.join(FONT_PATH, 'msyh.ttf');
const boldFontPath = path.join(FONT_PATH, 'SourceHanSansSC-Bold.otf');
// 保留宋体作为备选
const simsunPath = path.join(FONT_PATH, 'simsun.ttf');
const fallbackFontPath = path.join(FONT_PATH, 'simsun.ttc'); // 备选字体路径
// 输出找到的字体路径供调试 // 输出找到的字体路径供调试
console.log('FONT_PATH:', FONT_PATH); console.log('FONT_PATH:', FONT_PATH);
console.log('primaryFontPath:', primaryFontPath); console.log('msyhFontPath:', msyhFontPath, '存在?', fs.existsSync(msyhFontPath));
console.log('boldFontPath:', boldFontPath);
console.log('simsunPath:', simsunPath);
console.log('fallbackFontPath:', fallbackFontPath);
/** /**
* 服务层获取所有考生列表 * 服务层获取所有考生列表
@ -95,95 +88,43 @@ async function generatePdfService(pdfData, fileName) {
// 加载中文字体的标志 // 加载中文字体的标志
let chineseFontLoaded = false; let chineseFontLoaded = false;
let boldFontLoaded = false;
// 保存当前字体
let currentFont = null; let currentFont = null;
// 修改字体加载逻辑 // 专门的字体加载函数
try { function loadMicrosoftYaHeiFont() {
// 1. 尝试加载SourceHanSansSC常规字体 try {
if (fs.existsSync(primaryFontPath)) { // 只加载微软雅黑字体
try { if (fs.existsSync(msyhFontPath)) {
doc.registerFont('SourceHanSans', primaryFontPath); doc.registerFont('MicrosoftYaHei', msyhFontPath);
doc.font('SourceHanSans'); doc.font('MicrosoftYaHei');
currentFont = 'SourceHanSans'; currentFont = 'MicrosoftYaHei';
chineseFontLoaded = true; chineseFontLoaded = true;
console.log('成功加载SourceHanSansSC-Regular.otf字体'); console.log('成功加载msyh.ttf字体');
} catch (error) { return true;
console.error('加载SourceHanSansSC-Regular.otf字体失败:', error);
}
}
// 2. 尝试加载SourceHanSansSC粗体字体(用于标题)
if (fs.existsSync(boldFontPath)) {
try {
doc.registerFont('SourceHanSansBold', boldFontPath);
boldFontLoaded = true;
console.log('成功加载SourceHanSansSC-Bold.otf字体');
} catch (error) {
console.error('加载SourceHanSansSC-Bold.otf字体失败:', error);
}
}
// 3. 如果SourceHanSansSC字体加载失败尝试加载宋体
if (!chineseFontLoaded) {
if (fs.existsSync(simsunPath)) {
try {
doc.registerFont('SimSun', simsunPath);
doc.font('SimSun');
currentFont = 'SimSun';
chineseFontLoaded = true;
console.log('成功加载simsun.ttf字体');
} catch (ttfError) {
console.error('加载simsun.ttf字体失败:', ttfError);
// 尝试加载备选TTC字体
if (fs.existsSync(fallbackFontPath)) {
try {
doc.registerFont('SimSun', fallbackFontPath);
doc.font('SimSun');
currentFont = 'SimSun';
chineseFontLoaded = true;
console.log('成功加载simsun.ttc字体');
} catch (ttcError) {
console.error('加载simsun.ttc字体失败:', ttcError);
}
}
}
} else { } else {
console.warn(`未找到simsun.ttf字体文件: ${simsunPath}`); console.error('微软雅黑字体文件不存在:', msyhFontPath);
// 检查是否有备选TTC字体 return false;
if (fs.existsSync(fallbackFontPath)) {
try {
doc.registerFont('SimSun', fallbackFontPath);
doc.font('SimSun');
currentFont = 'SimSun';
chineseFontLoaded = true;
console.log('成功加载simsun.ttc字体');
} catch (error) {
console.error('加载simsun.ttc字体失败:', error);
}
}
} }
} catch (error) {
console.error('加载微软雅黑字体失败:', error);
return false;
} }
}
if (!chineseFontLoaded) { // 尝试加载字体
console.warn('无法加载中文字体,将使用默认字体,可能导致中文显示异常'); if (!loadMicrosoftYaHeiFont()) {
// 在macOS上尝试使用系统字体 console.warn('无法加载微软雅黑字体,将使用系统字体');
if (process.platform === 'darwin') { // 在macOS上尝试使用系统字体
try { if (process.platform === 'darwin') {
doc.font('Arial Unicode MS'); // macOS内置支持多语言的字体 try {
currentFont = 'Arial Unicode MS'; doc.font('Arial Unicode MS'); // macOS内置支持多语言的字体
chineseFontLoaded = true; currentFont = 'Arial Unicode MS';
console.log('成功加载系统Arial Unicode MS字体'); chineseFontLoaded = true;
} catch (error) { console.log('成功加载系统Arial Unicode MS字体');
console.error('加载系统字体失败:', error); } catch (error) {
} console.error('加载系统字体失败:', error);
} }
} }
} catch (error) {
console.error('加载字体失败:', error);
console.warn('将使用默认字体,可能导致中文显示异常');
} }
// 保存到文件 // 保存到文件
@ -195,14 +136,8 @@ async function generatePdfService(pdfData, fileName) {
// 保存当前字体 // 保存当前字体
const tempFont = currentFont; const tempFont = currentFont;
try { try {
// 尝试使用粗体字体 // 微软雅黑没有单独的粗体,使用普通字体但加大字号
if (boldFontLoaded) { doc.fontSize(20).text(pdfData.title, { align: 'center' }).moveDown();
doc.fontSize(20).font('SourceHanSansBold').text(pdfData.title, { align: 'center' }).moveDown();
} else if (process.platform === 'darwin') {
doc.fontSize(20).font('Arial Unicode MS Bold').text(pdfData.title, { align: 'center' }).moveDown();
} else {
doc.fontSize(20).text(pdfData.title, { align: 'center' }).moveDown();
}
} catch (error) { } catch (error) {
console.error('设置标题字体失败:', error); console.error('设置标题字体失败:', error);
doc.fontSize(20).text(pdfData.title, { align: 'center' }).moveDown(); doc.fontSize(20).text(pdfData.title, { align: 'center' }).moveDown();
@ -222,17 +157,10 @@ async function generatePdfService(pdfData, fileName) {
// 保存当前字体 // 保存当前字体
const tempFont = currentFont; const tempFont = currentFont;
try { try {
// 尝试使用SourceHanSansBold粗体字体 // 微软雅黑没有单独的粗体,使用普通字体但加大字号
if (boldFontLoaded) { doc.fontSize(item.fontSize || 16).text(item.text, item.options || {}).moveDown();
doc.fontSize(item.fontSize || 16).font('SourceHanSansBold').text(item.text, item.options || {}).moveDown();
} else if (process.platform === 'darwin') {
doc.fontSize(item.fontSize || 16).font('Arial Unicode MS Bold').text(item.text, item.options || {}).moveDown();
} else {
// 如果没有粗体字体,使用当前字体加大字号
doc.fontSize(item.fontSize || 16).text(item.text, item.options || {}).moveDown();
}
} catch (error) { } catch (error) {
console.error('切换到粗体字体失败:', error); console.error('切换到标题字体失败:', error);
doc.fontSize(item.fontSize || 16).text(item.text, item.options || {}).moveDown(); doc.fontSize(item.fontSize || 16).text(item.text, item.options || {}).moveDown();
} }
// 恢复之前的字体 // 恢复之前的字体
@ -279,30 +207,11 @@ async function generatePdfService(pdfData, fileName) {
doc.rect(marginLeft + i * cellWidth, currentY, cellWidth, cellHeight).stroke(); doc.rect(marginLeft + i * cellWidth, currentY, cellWidth, cellHeight).stroke();
// 保存当前字体 // 微软雅黑没有单独的粗体,使用普通字体
const tempFont = currentFont; doc.fontSize(fontSize).text(header, marginLeft + i * cellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, {
try { width: cellWidth - 10,
if (boldFontLoaded) { height: cellHeight - 10
doc.font('SourceHanSansBold'); });
} else if (process.platform === 'darwin') {
doc.font('Arial Unicode MS Bold');
}
// 垂直居中显示文本
doc.fontSize(fontSize).text(header, marginLeft + i * cellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, {
width: cellWidth - 10,
height: cellHeight - 10
});
} catch (error) {
console.error('设置表头字体失败:', error);
doc.fontSize(fontSize).text(header, marginLeft + i * cellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, {
width: cellWidth - 10,
height: cellHeight - 10
});
}
// 恢复字体
if (currentFont) {
doc.font(currentFont);
}
}); });
// 移动到下一行,考虑最高的表头单元格高度 // 移动到下一行,考虑最高的表头单元格高度
const headerLines = headers.map(header => calculateLines(header, cellWidth - 10).length); const headerLines = headers.map(header => calculateLines(header, cellWidth - 10).length);
@ -470,7 +379,6 @@ async function copyToDesk(filePath) {
* @param {string} jsonString - 包含试卷信息的JSON字符串 * @param {string} jsonString - 包含试卷信息的JSON字符串
* @returns {Promise<string>} - 生成的PDF文件绝对路径 * @returns {Promise<string>} - 生成的PDF文件绝对路径
*/ */
// 在generatePaperPdf函数中删除重复定义的字体路径变量
async function generatePaperPdf(jsonString) { async function generatePaperPdf(jsonString) {
try { try {
// 解析JSON字符串 // 解析JSON字符串
@ -543,57 +451,49 @@ async function generatePaperPdf(jsonString) {
margin: 50 margin: 50
}); });
// 加载中文字体 - 直接使用文件开头定义的全局变量,不要重新定义 // 加载中文字体 - 直接使用文件开头定义的全局变量
// 注意这里不再重新定义FONT_PATH等变量直接使用全局变量
let chineseFontLoaded = false; let chineseFontLoaded = false;
let boldFontLoaded = false;
let currentFont = null; let currentFont = null;
// 专门的字体加载函数
function loadMicrosoftYaHeiFont() {
try {
// 只加载微软雅黑字体
if (fs.existsSync(msyhFontPath)) {
doc.registerFont('MicrosoftYaHei', msyhFontPath);
doc.font('MicrosoftYaHei');
currentFont = 'MicrosoftYaHei';
chineseFontLoaded = true;
console.log('成功加载msyh.ttf字体');
return true;
} else {
console.error('微软雅黑字体文件不存在:', msyhFontPath);
return false;
}
} catch (error) {
console.error('加载微软雅黑字体失败:', error);
return false;
}
}
// 添加字体加载状态日志 // 添加字体加载状态日志
console.log('尝试加载字体:'); console.log('尝试加载字体:');
console.log('- 主要字体:', primaryFontPath, '存在?', fs.existsSync(primaryFontPath)); console.log('- 微软雅黑字体:', msyhFontPath, '存在?', fs.existsSync(msyhFontPath));
console.log('- 粗体字体:', boldFontPath, '存在?', fs.existsSync(boldFontPath));
console.log('- 宋体:', simsunPath, '存在?', fs.existsSync(simsunPath));
console.log('- 备选字体:', fallbackFontPath, '存在?', fs.existsSync(fallbackFontPath));
// 尝试加载字体 // 尝试加载字体
try { if (!loadMicrosoftYaHeiFont()) {
if (fs.existsSync(primaryFontPath)) { console.warn('无法加载微软雅黑字体,可能导致中文显示异常');
doc.registerFont('SourceHanSans', primaryFontPath); // 在macOS上尝试使用系统字体
doc.font('SourceHanSans'); if (process.platform === 'darwin') {
currentFont = 'SourceHanSans'; try {
chineseFontLoaded = true; doc.font('Arial Unicode MS');
console.log('成功加载主要字体'); currentFont = 'Arial Unicode MS';
} else if (fs.existsSync(simsunPath)) { chineseFontLoaded = true;
doc.registerFont('SimSun', simsunPath); console.log('使用系统默认字体 Arial Unicode MS');
doc.font('SimSun'); } catch (error) {
currentFont = 'SimSun'; console.error('加载系统字体失败:', error);
chineseFontLoaded = true; }
console.log('成功加载宋体');
} else if (fs.existsSync(fallbackFontPath)) {
doc.registerFont('SimSun', fallbackFontPath);
doc.font('SimSun');
currentFont = 'SimSun';
chineseFontLoaded = true;
console.log('成功加载备选字体');
} else if (process.platform === 'darwin') {
doc.font('Arial Unicode MS');
currentFont = 'Arial Unicode MS';
chineseFontLoaded = true;
console.log('使用系统默认字体 Arial Unicode MS');
} }
if (fs.existsSync(boldFontPath)) {
doc.registerFont('SourceHanSansBold', boldFontPath);
boldFontLoaded = true;
console.log('成功加载粗体字体');
}
if (!chineseFontLoaded) {
console.warn('无法加载中文字体,可能导致中文显示异常');
}
} catch (error) {
console.error('加载字体失败:', error);
} }
// 保存到文件 // 保存到文件
@ -601,11 +501,7 @@ async function generatePaperPdf(jsonString) {
doc.pipe(writeStream); doc.pipe(writeStream);
// 添加标题 // 添加标题
if (boldFontLoaded) { // 微软雅黑没有单独的粗体,使用普通字体但加大字号
doc.font('SourceHanSansBold');
} else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') {
doc.font('Arial Unicode MS Bold');
}
doc.fontSize(24).text('机考试卷', { align: 'center' }).moveDown(2); doc.fontSize(24).text('机考试卷', { align: 'center' }).moveDown(2);
// 恢复常规字体 // 恢复常规字体
@ -652,20 +548,11 @@ async function generatePaperPdf(jsonString) {
doc.rect(marginLeft + i * adjustedCellWidth, currentY, adjustedCellWidth, cellHeight).stroke(); doc.rect(marginLeft + i * adjustedCellWidth, currentY, adjustedCellWidth, cellHeight).stroke();
if (boldFontLoaded) { // 微软雅黑没有单独的粗体,使用普通字体
doc.font('SourceHanSansBold');
} else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') {
doc.font('Arial Unicode MS Bold');
}
doc.fontSize(fontSize).text(header, marginLeft + i * adjustedCellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, { doc.fontSize(fontSize).text(header, marginLeft + i * adjustedCellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, {
width: adjustedCellWidth - 10, width: adjustedCellWidth - 10,
height: cellHeight - 10 height: cellHeight - 10
}); });
if (currentFont) {
doc.font(currentFont);
}
}); });
// 移动到下一行(无间隙) // 移动到下一行(无间隙)
@ -737,17 +624,16 @@ async function generatePaperPdf(jsonString) {
['总题量', '试卷总分', '考试得分'], ['总题量', '试卷总分', '考试得分'],
[[totalQuestions.toString(), totalScore.toString(), realScore.toString()]] [[totalQuestions.toString(), totalScore.toString(), realScore.toString()]]
); );
// 留2行间距
doc.moveDown(2); doc.moveDown(2);
// 添加题目信息 // 添加题目信息
paperData.questions.forEach((question, index) => { paperData.questions.forEach((question, index) => {
// 题目类型和描述 // 题目类型和描述
if (boldFontLoaded) { // 上面留3行间距
doc.font('SourceHanSansBold'); doc.moveDown(3);
} else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') { // 微软雅黑没有单独的粗体,使用普通字体但加大字号
doc.font('Arial Unicode MS Bold'); doc.fontSize(14).text(`${index + 1} 题 (${question.question_type_name})`, doc.page.margins.left).moveDown(1); // 增加题干编号与上方内容的间距
}
doc.fontSize(14).text(`${index + 1} 题 (${question.question_type_name})`, doc.page.margins.left).moveDown();
if (currentFont) { if (currentFont) {
doc.font(currentFont); doc.font(currentFont);
@ -756,11 +642,12 @@ async function generatePaperPdf(jsonString) {
doc.fontSize(12).text(question.question_description, { doc.fontSize(12).text(question.question_description, {
x: doc.page.margins.left, x: doc.page.margins.left,
width: doc.page.width - doc.page.margins.left - doc.page.margins.right width: doc.page.width - doc.page.margins.left - doc.page.margins.right
}).moveDown(); });
// 留1行间距
doc.moveDown(1);
// 添加图片 // 添加图片
if (question.images && question.images.length > 0) { if (question.images && question.images.length > 0) {
doc.fontSize(12).text('图片:', doc.page.margins.left).moveDown(); // doc.fontSize(12).text('图片:', doc.page.margins.left).moveDown();
question.images.forEach((image) => { question.images.forEach((image) => {
if (image.image_base64) { if (image.image_base64) {
addBase64Image(image.image_base64); addBase64Image(image.image_base64);
@ -791,15 +678,16 @@ async function generatePaperPdf(jsonString) {
} }
}); });
} }
// 留1行间距
// doc.moveDown(1);
// 添加填空题 // 添加填空题
if (question.blanks && question.blanks.length > 0) { if (question.blanks && question.blanks.length > 0) {
question.blanks.forEach((blank, blankIndex) => { question.blanks.forEach((blank, blankIndex) => {
if (boldFontLoaded) { // 微软雅黑没有单独的粗体,使用普通字体但加大字号
doc.font('SourceHanSansBold'); // 对于第一个问题,减少与题干的间距
} else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') { // if (blankIndex === 0) {
doc.font('Arial Unicode MS Bold'); // doc.moveUp(5); // 向上调整一点,减少与题干的间距
} // }
doc.fontSize(12).text(`填空 ${blankIndex + 1}`, doc.page.margins.left).moveDown(); doc.fontSize(12).text(`填空 ${blankIndex + 1}`, doc.page.margins.left).moveDown();
if (currentFont) { if (currentFont) {
@ -822,17 +710,18 @@ async function generatePaperPdf(jsonString) {
], ],
100 100
); );
doc.moveDown(1);
}); });
} }
// 添加选择题 // 添加选择题
if (question.choices && question.choices.length > 0) { if (question.choices && question.choices.length > 0) {
question.choices.forEach((choice, choiceIndex) => { question.choices.forEach((choice, choiceIndex) => {
if (boldFontLoaded) { // 微软雅黑没有单独的粗体,使用普通字体但加大字号
doc.font('SourceHanSansBold'); // 对于第一个问题,减少与题干的间距
} else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') { // if (choiceIndex === 0) {
doc.font('Arial Unicode MS Bold'); // doc.moveUp(5); // 向上调整一点,减少与题干的间距
} // }
doc.fontSize(12).text(`选择题 ${choiceIndex + 1}`, doc.page.margins.left).moveDown(); doc.fontSize(12).text(`选择题 ${choiceIndex + 1}`, doc.page.margins.left).moveDown();
if (currentFont) { if (currentFont) {
@ -848,9 +737,11 @@ async function generatePaperPdf(jsonString) {
const optionsText = choice.choice_options.map((option, i) => { const optionsText = choice.choice_options.map((option, i) => {
const optionLabel = String.fromCharCode(65 + i); // A, B, C, D... const optionLabel = String.fromCharCode(65 + i); // A, B, C, D...
return `${optionLabel}. ${option}`; return `${optionLabel}. ${option}`;
}).join(' '); }).join(' '); // 使用多个空格作为选项间的间距
doc.fontSize(10).text(`选项: ${optionsText}`, doc.page.margins.left).moveDown(); doc.fontSize(10).text(`选项: ${optionsText}`, doc.page.margins.left);
} }
// 留一行间距
doc.moveDown(1);
drawTable( drawTable(
['正确答案', '考生答案', '分值', '得分'], ['正确答案', '考生答案', '分值', '得分'],
[ [
@ -863,6 +754,7 @@ async function generatePaperPdf(jsonString) {
], ],
100 100
); );
doc.moveDown(1);
}); });
} }