fix: use pdf-lib embedFont with proper pagination for CJK PDF

This commit is contained in:
Developer
2026-05-21 16:12:38 +08:00
parent d7cd5641d7
commit 0b0da09d4b
+32 -54
View File
@@ -4,11 +4,9 @@ import { PDFDocument, rgb, StandardFonts, PageSizes } from 'pdf-lib';
const FONT_SEARCH_PATHS = [
path.join(__dirname, '..', '..', '..', 'assets', 'fonts', 'NotoSansSC-VF.ttf'),
'C:/Windows/Fonts/msyh.ttc',
'C:/Windows/Fonts/msyhbd.ttc',
'C:/Windows/Fonts/simsun.ttc',
'C:/Windows/Fonts/msyh.ttf',
'C:/Windows/Fonts/simsun.ttf',
'/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
'/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc',
];
let cachedFontBytes: Buffer | null = null;
@@ -61,59 +59,39 @@ export async function generateAssessmentPdf(options: PdfReportOptions): Promise<
const sectionSize = 13;
const lineHeight = fontSize * 1.6;
function drawPage(title: string, titleSize: number, sections: PdfSection[]): void {
const page = doc.addPage([pageWidth, pageHeight]);
let y = pageHeight - margin;
let page = doc.addPage([pageWidth, pageHeight]);
let y = pageHeight - margin;
page.drawText(title, { x: margin, y, size: titleSize, font, color: rgb(0, 0, 0) });
y -= titleSize * 1.8;
if (options.subtitle) {
page.drawText(options.subtitle, { x: margin, y, size: 9, font, color: rgb(0.4, 0.4, 0.4) });
y -= 16;
}
for (const section of sections) {
y -= 8;
if (y < margin + sectionSize * 2) {
y = pageHeight - margin;
}
page.drawText(section.title, { x: margin, y, size: sectionSize, font, color: rgb(0.1, 0.1, 0.1) });
y -= sectionSize * 1.8;
for (const line of section.lines) {
const chunks = wrapLine(line, font, fontSize, pageWidth - margin * 2);
for (const chunk of chunks) {
if (y < margin + lineHeight) {
y = pageHeight - margin;
}
page.drawText(chunk, { x: margin, y, size: fontSize, font, color: rgb(0.2, 0.2, 0.2) });
y -= lineHeight;
}
}
}
page.drawText('--- End of Report ---', { x: margin, y: margin, size: 8, font, color: rgb(0.6, 0.6, 0.6) });
function newPage() {
page = doc.addPage([pageWidth, pageHeight]);
y = pageHeight - margin;
}
drawPage(options.title, titleSize, options.sections);
function drawText(text: string, size: number, color: any, offsetY: number) {
if (y < margin + offsetY) newPage();
page.drawText(text, { x: margin, y, size, font, color });
y -= offsetY;
}
drawText(options.title, titleSize, rgb(0, 0, 0), titleSize * 1.8);
if (options.subtitle) {
drawText(options.subtitle, 9, rgb(0.4, 0.4, 0.4), 16);
}
for (const section of options.sections) {
y -= 8;
drawText(section.title, sectionSize, rgb(0.1, 0.1, 0.1), sectionSize * 1.8);
for (const line of section.lines) {
if (!line) continue;
for (const chunk of line.split('\n')) {
drawText(chunk || ' ', fontSize, rgb(0.2, 0.2, 0.2), lineHeight);
}
}
}
drawText('--- End of Report ---', 8, rgb(0.6, 0.6, 0.6), 20);
return Buffer.from(await doc.save());
}
function wrapLine(text: string, _font: any, fontSize: number, maxWidth: number): string[] {
if (!text || text.length === 0) return [''];
const chunks: string[] = [];
const lines = text.split('\n');
for (const line of lines) {
if (line.length === 0) {
chunks.push('');
continue;
}
const estimatedCharsPerLine = Math.max(1, Math.floor(maxWidth / (fontSize * 0.6)));
for (let i = 0; i < line.length; i += estimatedCharsPerLine) {
chunks.push(line.substring(i, i + estimatedCharsPerLine));
}
}
return chunks.length > 0 ? chunks : [text];
}