Files
huashu-design/scripts/export_deck_pdf.mjs
T
alchainandClaude Opus 4.7 7d7bab4a82 init: huashu-design skill v1.0
花叔设计工作台 —— AI 驱动的 HTML 设计工作流,支持 Claude Code / Codex /
Cursor / Trae / OpenClaw / Hermes Agent 等任意 skill-compatible agent。

四条主干能力:
- HTML 高保真视觉产出(原型/App/幻灯片/动画/变体/信息图)
- 设计方向顾问 Fallback(5 流派 × 20 设计哲学 × 24 预制 showcase)
- Junior Designer 工作流(先 show 假设再迭代)
- 专家级 5 维度评审(哲学/层级/执行/功能/创新)

工程工具链:
- HTML → 25fps/60fps MP4 + palette GIF,含 6 首场景化 BGM
- HTML → PDF / PPTX 导出
- Playwright 截图 + 控制台错误验证
- iOS/Android/macOS/Browser 设备边框 + Stage/Sprite 动画引擎

初版包含 SKILL.md + 13 份 references + 7 个 scripts + 18 类 assets。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 22:41:51 +08: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 做任何改造
* - 视觉损失 = 0(PDF 就是浏览器打印出来的)
*
* 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); });