import * as fs from 'fs'; import * as path from 'path'; import { PDFDocument, rgb, StandardFonts, PageSizes } from 'pdf-lib'; const FONT_SEARCH_PATHS = [ 'C:/Windows/Fonts/NotoSansSC-VF.ttf', 'C:/Windows/Fonts/NotoSansJP-VF.ttf', path.join(__dirname, '..', '..', '..', 'assets', 'fonts', 'NotoSansSC-VF.ttf'), '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', ]; let cachedFontBytes: Buffer | null = null; function findFont(): Buffer { if (cachedFontBytes) return cachedFontBytes; for (const p of FONT_SEARCH_PATHS) { try { if (fs.existsSync(p)) { cachedFontBytes = fs.readFileSync(p); return cachedFontBytes; } } catch { } } return Buffer.alloc(0); } interface PdfReportOptions { title: string; subtitle?: string; sections: PdfSection[]; } interface PdfSection { title: string; lines: string[]; } export async function generateAssessmentPdf(options: PdfReportOptions): Promise { const doc = await PDFDocument.create(); let font: any; const fontBytes = findFont(); if (fontBytes.length > 0) { try { font = await doc.embedFont(fontBytes, { subset: true }); } catch { font = undefined; } } if (!font) { font = await doc.embedFont(StandardFonts.Helvetica); } const pageWidth = PageSizes.A4[0]; const pageHeight = PageSizes.A4[1]; const margin = 50; const fontSize = 10; const titleSize = 20; const sectionSize = 13; const lineHeight = fontSize * 1.6; let page = doc.addPage([pageWidth, pageHeight]); let y = pageHeight - margin; function newPage() { page = doc.addPage([pageWidth, pageHeight]); y = pageHeight - margin; } 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()); }