193 lines
6.1 KiB
JavaScript
193 lines
6.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Generate output HTML from template + data for cargo-filling documents.
|
|
*
|
|
* Usage: node generate.mjs <docId>
|
|
* Example: node generate.mjs cargo-filling-1
|
|
*
|
|
* Reads: docs/<docId>.template.html
|
|
* Reads: docs/<docId>.data.json (optional)
|
|
* Writes: docs/<docId>.output.html
|
|
*
|
|
* data.json format:
|
|
* {
|
|
* pages: [{
|
|
* page: 1,
|
|
* cards: [{
|
|
* index: 0,
|
|
* asteroidScale: 1.15,
|
|
* ship: { right: -10, top: -3, width: 85, height: 40 },
|
|
* innerAst: { left: 60, top: 70 }
|
|
* }]
|
|
* }]
|
|
* }
|
|
*
|
|
* Transforms:
|
|
* - asteroidScale: scale on .space-asteroid img, .remnant-shape .shape-container, .inner-ast
|
|
* - ship: inline right/top/width/height on .cargo-ship
|
|
* - innerAst: inline left/top on .inner-ast (composed with translate and optional scale)
|
|
*/
|
|
|
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
import { join, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { postGenerate } from '../../../src/scripts/post-generate.mjs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const docsDir = join(__dirname, '..', 'docs');
|
|
|
|
const docId = process.argv[2];
|
|
if (!docId) {
|
|
console.error('Usage: node generate.mjs <docId>');
|
|
process.exit(1);
|
|
}
|
|
|
|
const templatePath = join(docsDir, `${docId}.template.html`);
|
|
const dataPath = join(docsDir, `${docId}.data.json`);
|
|
const outputPath = join(docsDir, `${docId}.output.html`);
|
|
|
|
if (!existsSync(templatePath)) {
|
|
console.error(`Template not found: ${templatePath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
let html = readFileSync(templatePath, 'utf-8');
|
|
|
|
if (existsSync(dataPath)) {
|
|
const data = JSON.parse(readFileSync(dataPath, 'utf-8'));
|
|
html = applyData(html, data);
|
|
console.log(`Applied data from ${data.pages?.length || 0} pages`);
|
|
}
|
|
|
|
writeFileSync(outputPath, html);
|
|
console.log(`Generated: ${outputPath}`);
|
|
await postGenerate(outputPath);
|
|
|
|
function applyData(html, data) {
|
|
if (!data.pages) return html;
|
|
|
|
// Split HTML into pages by w-[210mm] h-[297mm] divs
|
|
const pageRegex = /<div class="w-\[210mm\] h-\[297mm\]/g;
|
|
const starts = [];
|
|
let match;
|
|
while ((match = pageRegex.exec(html)) !== null) {
|
|
starts.push(match.index);
|
|
}
|
|
if (starts.length === 0) return html;
|
|
|
|
// Process pages in reverse to preserve indices
|
|
for (let i = data.pages.length - 1; i >= 0; i--) {
|
|
const pageData = data.pages[i];
|
|
const pageNum = pageData.page || (i + 1);
|
|
const pageIdx = pageNum - 1;
|
|
if (pageIdx >= starts.length) continue;
|
|
|
|
const pageStart = starts[pageIdx];
|
|
const pageEnd = pageIdx + 1 < starts.length ? starts[pageIdx + 1] : html.length;
|
|
let pageHtml = html.slice(pageStart, pageEnd);
|
|
|
|
if (pageData.cards) {
|
|
pageHtml = applyCards(pageHtml, pageData.cards);
|
|
}
|
|
|
|
html = html.slice(0, pageStart) + pageHtml + html.slice(pageEnd);
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
function applyCards(pageHtml, cards) {
|
|
// Find card boundaries by <!-- Card comments (two formats: "Card 1:" and "Card:")
|
|
const cardRegex = /<!-- Card[\s:]/g;
|
|
const cardStarts = [];
|
|
let match;
|
|
while ((match = cardRegex.exec(pageHtml)) !== null) {
|
|
cardStarts.push(match.index);
|
|
}
|
|
if (cardStarts.length === 0) return pageHtml;
|
|
|
|
// Process cards in reverse to preserve indices
|
|
for (let i = cards.length - 1; i >= 0; i--) {
|
|
const cardData = cards[i];
|
|
const cardIdx = cardData.index;
|
|
if (cardIdx >= cardStarts.length) continue;
|
|
|
|
const cardStart = cardStarts[cardIdx];
|
|
const cardEnd = cardIdx + 1 < cardStarts.length
|
|
? cardStarts[cardIdx + 1]
|
|
: pageHtml.length;
|
|
|
|
let cardHtml = pageHtml.slice(cardStart, cardEnd);
|
|
|
|
const scale = cardData.asteroidScale;
|
|
const ship = cardData.ship;
|
|
const innerAst = cardData.innerAst;
|
|
|
|
// Apply asteroidScale to .space-asteroid img
|
|
if (scale != null && scale !== 1) {
|
|
// Match <div class="space-asteroid"> then find the <img inside
|
|
cardHtml = cardHtml.replace(
|
|
/(<div class="space-asteroid">[\s]*<img [^>]*?)(>)/,
|
|
function(full, before, close) {
|
|
// Remove any existing style, then add new one
|
|
const cleaned = before.replace(/ style="[^"]*"/, '');
|
|
return cleaned + ' style="transform: scale(' + scale + ')"' + close;
|
|
}
|
|
);
|
|
}
|
|
|
|
// Apply asteroidScale to .remnant-shape .shape-container
|
|
if (scale != null && scale !== 1) {
|
|
cardHtml = cardHtml.replace(
|
|
/<div class="shape-container">/,
|
|
'<div class="shape-container" style="transform: scale(' + scale + ')">'
|
|
);
|
|
}
|
|
|
|
// Apply ship position/size to .cargo-ship
|
|
if (ship) {
|
|
const shipRight = ship.right != null ? ship.right : -12.5;
|
|
const shipTop = ship.top != null ? ship.top : -5;
|
|
const shipWidth = ship.width != null ? ship.width : 90;
|
|
const shipHeight = ship.height != null ? ship.height : 42;
|
|
const shipStyle = 'right: ' + shipRight + 'mm; top: ' + shipTop + 'mm; width: ' + shipWidth + 'mm; height: ' + shipHeight + 'mm';
|
|
|
|
cardHtml = cardHtml.replace(
|
|
/(<div class="cargo-ship")([\s>])/,
|
|
function(full, tag, after) {
|
|
return tag + ' style="' + shipStyle + '"' + after;
|
|
}
|
|
);
|
|
}
|
|
|
|
// Apply innerAst position (and compose with asteroidScale if present)
|
|
if (innerAst || (scale != null && scale !== 1)) {
|
|
const innerLeft = innerAst && innerAst.left != null ? innerAst.left : 58;
|
|
const innerTop = innerAst && innerAst.top != null ? innerAst.top : 73;
|
|
|
|
let transformPart = 'translate(-50%, -50%)';
|
|
if (scale != null && scale !== 1) {
|
|
transformPart = 'translate(-50%, -50%) scale(' + scale + ')';
|
|
}
|
|
|
|
let innerStyle = 'left: ' + innerLeft + '%; top: ' + innerTop + '%; transform: ' + transformPart;
|
|
|
|
// Only apply if we actually have something to change
|
|
const needsInnerChange = (innerAst && (innerAst.left != null || innerAst.top != null)) || (scale != null && scale !== 1);
|
|
if (needsInnerChange) {
|
|
cardHtml = cardHtml.replace(
|
|
/(<div class="inner-ast")([\s>])/,
|
|
function(full, tag, after) {
|
|
return tag + ' style="' + innerStyle + '"' + after;
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
pageHtml = pageHtml.slice(0, cardStart) + cardHtml + pageHtml.slice(cardEnd);
|
|
}
|
|
|
|
return pageHtml;
|
|
}
|