import puppeteer from 'puppeteer'; import { readFile, writeFile, access } from 'fs/promises'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; import nunjucks from 'nunjucks'; import matter from 'gray-matter'; const __dir = dirname(fileURLToPath(import.meta.url)); const root = resolve(__dir, '..'); // KDP constants — Premium Color paper, 8.5×8.5 in trim const BLEED_IN = 0.125; const TRIM_W_IN = 8.5; const TRIM_H_IN = 8.5; const SPINE_PER_PAGE = 0.002347; // inches per page, Premium Color const SAFE_IN = 0.125; // safe zone inset from trim edge (front/back) const SPINE_SAFE_IN = 0.0625; // safe zone inset from spine fold (each side) const MIN_SPINE_TEXT = 79; // KDP minimum pages for spine text async function fileExists(p) { try { await access(p); return true; } catch { return false; } } async function generate() { // Read page count produced by build.js const metaPath = resolve(root, 'output', 'book-meta.json'); let pageCount = 100; if (await fileExists(metaPath)) { const meta = JSON.parse(await readFile(metaPath, 'utf-8')); pageCount = meta.pageCount; console.log(`Page count: ${pageCount} (from output/book-meta.json)`); } else { console.warn(`book-meta.json not found — using default page count ${pageCount}`); console.warn('Run `pnpm build` first for an accurate spine width.\n'); } // Compute dimensions const spineWidth = pageCount * SPINE_PER_PAGE; const totalWidth = BLEED_IN + TRIM_W_IN + spineWidth + TRIM_W_IN + BLEED_IN; const totalHeight = BLEED_IN + TRIM_H_IN + BLEED_IN; console.log(`Spine width : ${spineWidth.toFixed(4)} in`); console.log(`Cover canvas: ${totalWidth.toFixed(4)} × ${totalHeight.toFixed(4)} in\n`); // Check for optional artwork files const frontImage = await fileExists(resolve(root, 'images/cover/front.png')) ? '../images/cover/front.png' : null; const backImage = await fileExists(resolve(root, 'images/cover/back.png')) ? '../images/cover/back.png' : null; // Load author / title from front matter const fmRaw = await readFile(resolve(root, 'content', '00-front-matter.md'), 'utf-8'); const { data: fm } = matter(fmRaw); // Render HTML from template const env = nunjucks.configure(resolve(root, 'templates'), { autoescape: false }); const html = env.render('cover.html', { pageCount, spineWidth: spineWidth.toFixed(4), totalWidth: totalWidth.toFixed(4), totalHeight: totalHeight.toFixed(4), bleed: BLEED_IN, trimW: TRIM_W_IN, trimH: TRIM_H_IN, safe: SAFE_IN, spineSafe: SPINE_SAFE_IN, hasSpineText: pageCount >= MIN_SPINE_TEXT, frontImage, backImage, author: fm.author || '', title: fm.title || 'Das Kaleidoskop der Schlummerwelten', subtitle: fm.subtitle || '', }); const htmlOut = resolve(root, 'output', 'cover.html'); await writeFile(htmlOut, html, 'utf-8'); // Generate PDF const systemChromium = '/usr/bin/chromium'; const useSystem = await fileExists(systemChromium); console.log(`Launching Puppeteer (${useSystem ? 'system Chromium' : 'bundled Chrome'})…`); const browser = await puppeteer.launch({ headless: true, ...(useSystem ? { executablePath: systemChromium } : {}), }); const page = await browser.newPage(); await page.goto(`file://${htmlOut}`, { waitUntil: 'networkidle0' }); const pdfOut = resolve(root, 'output', 'cover.pdf'); await page.pdf({ path: pdfOut, width: `${totalWidth}in`, height: `${totalHeight}in`, printBackground: true, margin: { top: 0, bottom: 0, left: 0, right: 0 }, }); await browser.close(); console.log('Cover PDF written to output/cover.pdf'); console.log('\nNote: KDP recommends CMYK for covers. This PDF is RGB.'); console.log('Convert with Adobe Acrobat or Ghostscript before upload if needed.'); } generate().catch(err => { console.error(err); process.exit(1); });