96ba26559b
Replaces Georgia with Cormorant Garamond (display, 300 italic) + Lora (body) across all cover text. Adds @font-face declarations pointing to fonts/ (same as interior). Updates gold tokens to match interior palette. Visual changes: front title in Cormorant light italic with gold gradient rule below, ❧ ornament replacing ✦, synopsis body in Lora. cover.js now reads 00-front-matter.md to inject author name into spine, front, and removes the [AUTOR] placeholder. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
108 lines
3.9 KiB
JavaScript
108 lines
3.9 KiB
JavaScript
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); });
|