146 lines
4.5 KiB
JavaScript
146 lines
4.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Generate output HTML from template + data for asteroid-splitting documents.
|
|
*
|
|
* Usage: node generate.mjs <docId>
|
|
* Example: node generate.mjs asteroid-splitting-1
|
|
*
|
|
* Reads: docs/<docId>.template.html
|
|
* Reads: docs/<docId>.data.json (optional)
|
|
* Writes: docs/<docId>.output.html
|
|
*
|
|
* data.json format:
|
|
* {
|
|
* pages: [{
|
|
* page: 1,
|
|
* sections: [{ index: 0, scale: 1.2, rotate: 15 }]
|
|
* }]
|
|
* }
|
|
*
|
|
* Applies transform to big asteroid img (w-[26mm] container)
|
|
* and proportionally to small asteroid imgs (w-[12mm] containers) in formula rows.
|
|
*/
|
|
|
|
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] 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.sections) {
|
|
pageHtml = applySections(pageHtml, pageData.sections);
|
|
}
|
|
|
|
html = html.slice(0, pageStart) + pageHtml + html.slice(pageEnd);
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
function applySections(pageHtml, sections) {
|
|
// Find section containers: <!-- Section: N=... -->
|
|
// Each section has a big asteroid img in .asteroid-glow and small imgs in formula rows
|
|
|
|
// Find all asteroid-glow containers (one per section)
|
|
const sectionBlocks = [];
|
|
const sectionRegex = /<!-- Section: N=\d+/g;
|
|
let match;
|
|
while ((match = sectionRegex.exec(pageHtml)) !== null) {
|
|
sectionBlocks.push(match.index);
|
|
}
|
|
|
|
// Process sections in reverse
|
|
for (let i = sections.length - 1; i >= 0; i--) {
|
|
const secData = sections[i];
|
|
const secIdx = secData.index;
|
|
if (secIdx >= sectionBlocks.length) continue;
|
|
|
|
const secStart = sectionBlocks[secIdx];
|
|
const secEnd = secIdx + 1 < sectionBlocks.length
|
|
? sectionBlocks[secIdx + 1]
|
|
: pageHtml.length;
|
|
|
|
let secHtml = pageHtml.slice(secStart, secEnd);
|
|
|
|
const scale = secData.scale ?? 1;
|
|
const rotate = secData.rotate ?? 0;
|
|
|
|
if (scale !== 1 || rotate !== 0) {
|
|
const transform = `transform: scale(${scale}) rotate(${rotate}deg);`;
|
|
|
|
// Apply to big asteroid img (inside asteroid-glow container)
|
|
// The img has class="w-full h-full object-contain"
|
|
// Add style to the img inside asteroid-glow
|
|
secHtml = secHtml.replace(
|
|
/(class="w-full h-full object-contain" alt="")(>)(\s*<span class="absolute text-\[2\.2rem\])/,
|
|
`$1 style="${transform}"$2$3`
|
|
);
|
|
|
|
// Apply to all small asteroid imgs (inside w-[12mm] containers)
|
|
// These imgs also have class="w-full h-full object-contain"
|
|
// but are inside relative w-[12mm] divs
|
|
secHtml = secHtml.replace(
|
|
/(<div class="relative w-\[12mm\] h-\[12mm\] shrink-0"><img src="[^"]*pack3-asteroids[^"]*" class="w-full h-full object-contain" alt="")>/g,
|
|
`$1 style="${transform}">`
|
|
);
|
|
}
|
|
|
|
pageHtml = pageHtml.slice(0, secStart) + secHtml + pageHtml.slice(secEnd);
|
|
}
|
|
|
|
return pageHtml;
|
|
}
|