Give the Word package a real page header for the CI band
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// Render the corporate identity band used by the delivery package.
|
||||
//
|
||||
// The band is the same assets/header.html the PDF is printed with, captured as
|
||||
// an image so its ribbon geometry survives into Word, where it is placed in a
|
||||
// real page header instead of being reconstructed in the body.
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.resolve(here, '../..');
|
||||
const assets = path.join(here, 'assets');
|
||||
|
||||
export const BAND_MM = { width: 210, height: 27 };
|
||||
const SCALE = 3; // the band is drawn at 3x so it stays crisp in print
|
||||
|
||||
const applyTemplate = (template, values) => template.replace(/{{(\w+)}}/g, (_, key) => values[key] ?? '');
|
||||
const slug = (title) => title.replace(/[^\w-]+/g, '-').replace(/^-|-$/g, '').toLowerCase() || 'band';
|
||||
|
||||
async function markdownFiles(directory) {
|
||||
const entries = await fs.readdir(directory, { withFileTypes: true });
|
||||
const results = await Promise.all(entries.map(async (entry) => {
|
||||
const target = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) return markdownFiles(target);
|
||||
return entry.isFile() && entry.name.endsWith('.md') ? [target] : [];
|
||||
}));
|
||||
return results.flat();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const [sourceRoot, outputRoot] = process.argv.slice(2);
|
||||
if (!sourceRoot || !outputRoot) throw new Error('Usage: render-ci-band.mjs SOURCE_DIRECTORY OUTPUT_DIRECTORY');
|
||||
|
||||
const [template, logo] = await Promise.all([
|
||||
fs.readFile(path.join(assets, 'header.html'), 'utf8'),
|
||||
fs.readFile(path.join(root, 'app/assets/images/logo.svg'))
|
||||
]);
|
||||
const values = {
|
||||
logoPath: `data:image/svg+xml;base64,${logo.toString('base64')}`,
|
||||
ciBlue: '#0797d5',
|
||||
ciGrey: '#8e8e8e' // ribbon grey measured from the audited reference documents
|
||||
};
|
||||
|
||||
await fs.mkdir(outputRoot, { recursive: true });
|
||||
const sources = (await markdownFiles(sourceRoot)).sort();
|
||||
const bands = {};
|
||||
const rendered = new Map();
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
try {
|
||||
const page = await browser.newPage({ deviceScaleFactor: SCALE });
|
||||
for (const source of sources) {
|
||||
const markdown = await fs.readFile(source, 'utf8');
|
||||
const title = markdown.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? 'BRN WMS';
|
||||
const footer = markdown.match(/^<!--\s*footer:\s*(.+?)\s*-->\s*$/m)?.[1] ?? '';
|
||||
|
||||
if (!rendered.has(title)) {
|
||||
const file = path.join(outputRoot, `${slug(title)}.png`);
|
||||
await page.setContent(
|
||||
'<!doctype html><meta charset="utf-8"><body style="margin:0;background:#fff">' +
|
||||
`<div id="band" style="width:${BAND_MM.width}mm;height:${BAND_MM.height}mm;overflow:hidden;position:relative">` +
|
||||
`${applyTemplate(template, { ...values, documentTitle: title })}</div></body>`,
|
||||
{ waitUntil: 'networkidle' }
|
||||
);
|
||||
await page.locator('#band').screenshot({ type: 'png', path: file });
|
||||
rendered.set(title, file);
|
||||
}
|
||||
|
||||
bands[path.relative(sourceRoot, source)] = { title, footer, band: rendered.get(title) };
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
await fs.writeFile(path.join(outputRoot, 'bands.json'), `${JSON.stringify(bands, null, 2)}\n`);
|
||||
console.log(`${rendered.size} band${rendered.size === 1 ? '' : 's'} rendered for ${sources.length} documents`);
|
||||
}
|
||||
|
||||
main().catch((error) => { console.error(error); process.exit(1); });
|
||||
Reference in New Issue
Block a user