HH/.agents/skills/huashu-design/scripts/export_deck_pdf.mjs
ismail c5f76b3855
Some checks are pending
Build and Push Docker Image / build (push) Waiting to run
updates
2026-05-11 14:45:30 +03:00

99 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* export_deck_pdf.mjs — 把多文件 slide deck 导出为单个矢量 PDF
*
* 用法:
* node export_deck_pdf.mjs --slides <dir> --out <file.pdf> [--width 1920] [--height 1080]
*
* 特点:
* - 文字保留矢量(可复制、可搜索)
* - 背景/图形 1:1 保真Playwright 内嵌 Chromium 渲染)
* - 不需要对 HTML 做任何改造
* - 视觉损失 = 0PDF 就是浏览器打印出来的)
*
* trade-off
* - PDF 不可再编辑文字(要改回到 HTML 改)
*
* 依赖playwright pdf-lib
* npm install playwright pdf-lib
*
* 会按文件名排序01-xxx.html → 02-xxx.html → ...
*/
import { chromium } from 'playwright';
import { PDFDocument } from 'pdf-lib';
import fs from 'fs/promises';
import path from 'path';
function parseArgs() {
const args = { width: 1920, height: 1080 };
const a = process.argv.slice(2);
for (let i = 0; i < a.length; i += 2) {
const k = a[i].replace(/^--/, '');
args[k] = a[i + 1];
}
if (!args.slides || !args.out) {
console.error('用法: node export_deck_pdf.mjs --slides <dir> --out <file.pdf> [--width 1920] [--height 1080]');
process.exit(1);
}
args.width = parseInt(args.width);
args.height = parseInt(args.height);
return args;
}
async function main() {
const { slides, out, width, height } = parseArgs();
const slidesDir = path.resolve(slides);
const outFile = path.resolve(out);
const files = (await fs.readdir(slidesDir))
.filter(f => f.endsWith('.html'))
.sort();
if (!files.length) {
console.error(`No .html files found in ${slidesDir}`);
process.exit(1);
}
console.log(`Found ${files.length} slides in ${slidesDir}`);
const browser = await chromium.launch();
const ctx = await browser.newContext({ viewport: { width, height } });
// 1) Render each HTML to its own PDF buffer
const pageBuffers = [];
for (const f of files) {
const page = await ctx.newPage();
const url = 'file://' + path.join(slidesDir, f);
await page.goto(url, { waitUntil: 'networkidle' }).catch(() => page.goto(url));
await page.waitForTimeout(1200); // web-font paint
// emulate "screen" so CSS colors/backgrounds render the same as browser
await page.emulateMedia({ media: 'screen' });
const buf = await page.pdf({
width: `${width}px`,
height: `${height}px`,
printBackground: true,
margin: { top: 0, right: 0, bottom: 0, left: 0 },
preferCSSPageSize: false,
});
pageBuffers.push(buf);
await page.close();
console.log(` [${pageBuffers.length}/${files.length}] ${f}`);
}
await browser.close();
// 2) Merge into a single PDF
const merged = await PDFDocument.create();
for (const buf of pageBuffers) {
const src = await PDFDocument.load(buf);
const copied = await merged.copyPages(src, src.getPageIndices());
copied.forEach(p => merged.addPage(p));
}
const bytes = await merged.save();
await fs.writeFile(outFile, bytes);
const kb = (bytes.byteLength / 1024).toFixed(0);
console.log(`\n✓ Wrote ${outFile} (${kb} KB, ${files.length} pages, vector)`);
}
main().catch(e => { console.error(e); process.exit(1); });