98 lines
2.4 KiB
TypeScript
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());
|
|
}
|