104 lines
2.8 KiB
JavaScript
104 lines
2.8 KiB
JavaScript
import puppeteer from 'puppeteer';
|
|
import { resolve, basename, dirname, extname } from 'path';
|
|
import { existsSync, mkdirSync } from 'fs';
|
|
import { fileURLToPath } from 'url';
|
|
import { createServer } from 'http';
|
|
import { readFile } from 'fs/promises';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const PROJECT_ROOT = resolve(__dirname, '..');
|
|
const OUTPUT_DIR = resolve(PROJECT_ROOT, 'output/pdf');
|
|
|
|
const MIME_TYPES = {
|
|
'.html': 'text/html',
|
|
'.css': 'text/css',
|
|
'.js': 'application/javascript',
|
|
'.mjs': 'application/javascript',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.jpeg': 'image/jpeg',
|
|
'.webp': 'image/webp',
|
|
'.svg': 'image/svg+xml',
|
|
'.json': 'application/json',
|
|
'.woff': 'font/woff',
|
|
'.woff2': 'font/woff2',
|
|
'.ttf': 'font/ttf',
|
|
};
|
|
|
|
async function generatePdf(htmlPath, outDirOverride) {
|
|
const absolutePath = resolve(htmlPath);
|
|
if (!existsSync(absolutePath)) {
|
|
console.error(`File not found: ${absolutePath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const outDir = outDirOverride ? resolve(outDirOverride) : OUTPUT_DIR;
|
|
mkdirSync(outDir, { recursive: true });
|
|
|
|
const pdfName = basename(absolutePath, '.html') + '.pdf';
|
|
const pdfPath = resolve(outDir, pdfName);
|
|
|
|
// Serve the project root so the HTML can load /templates/cv-style.css and any other assets.
|
|
const server = createServer(async (req, res) => {
|
|
const filePath = resolve(
|
|
PROJECT_ROOT,
|
|
decodeURIComponent(req.url).replace(/^\//, ''),
|
|
);
|
|
try {
|
|
const data = await readFile(filePath);
|
|
const ext = extname(filePath);
|
|
res.writeHead(200, {
|
|
'Content-Type': MIME_TYPES[ext] || 'application/octet-stream',
|
|
});
|
|
res.end(data);
|
|
} catch {
|
|
res.writeHead(404);
|
|
res.end('Not found');
|
|
}
|
|
});
|
|
|
|
await new Promise((r) => server.listen(0, '127.0.0.1', r));
|
|
const port = server.address().port;
|
|
const relPath = absolutePath.replace(PROJECT_ROOT + '/', '');
|
|
|
|
const browser = await puppeteer.launch({
|
|
headless: true,
|
|
args: ['--no-sandbox'],
|
|
});
|
|
const page = await browser.newPage();
|
|
|
|
await page.goto(`http://127.0.0.1:${port}/${relPath}`, {
|
|
waitUntil: 'networkidle0',
|
|
timeout: 30000,
|
|
});
|
|
|
|
// Wait for web fonts (if any) to be ready before rasterizing.
|
|
await page.evaluate(async () => {
|
|
if (document.fonts && document.fonts.ready) {
|
|
await document.fonts.ready;
|
|
}
|
|
});
|
|
|
|
await page.pdf({
|
|
path: pdfPath,
|
|
format: 'A4',
|
|
printBackground: true,
|
|
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
preferCSSPageSize: true,
|
|
});
|
|
|
|
await browser.close();
|
|
server.close();
|
|
console.log(`PDF generated: ${pdfPath}`);
|
|
return pdfPath;
|
|
}
|
|
|
|
const args = process.argv.slice(2);
|
|
if (args.length === 0) {
|
|
console.error('Usage: node generate-pdf.mjs <html-file> [out-dir]');
|
|
process.exit(1);
|
|
}
|
|
|
|
const [htmlFile, outDir] = args;
|
|
generatePdf(htmlFile, outDir);
|