Files
aurak/server/src/assessment/services/pdf-generator.ts
T

98 lines
2.4 KiB
TypeScript

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<Buffer> {
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());
}