From 14ee36419036b61152f35853a8d3d4f774379a77 Mon Sep 17 00:00:00 2001 From: dotta Date: Fri, 20 Mar 2026 05:55:33 -0500 Subject: [PATCH] Add standalone Playwright-based org chart image generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the sharp SVG→PNG approach with Playwright headless browser rendering. This solves the emoji rendering issue - browser natively renders emojis, full CSS (shadows, gradients, backdrop-filter), and produces pixel-perfect output matching the HTML preview. Generates 20 images: 5 styles (Mono, Nebula, Circuit, Warmth, Schematic) × 3 org sizes (sm/med/lg) + OG cards (1200×630). Usage: npx tsx scripts/generate-org-chart-images.ts Co-Authored-By: Paperclip --- scripts/generate-org-chart-images.ts | 694 +++++++++++++++++++++++++++ 1 file changed, 694 insertions(+) create mode 100644 scripts/generate-org-chart-images.ts diff --git a/scripts/generate-org-chart-images.ts b/scripts/generate-org-chart-images.ts new file mode 100644 index 00000000..f60e7d1f --- /dev/null +++ b/scripts/generate-org-chart-images.ts @@ -0,0 +1,694 @@ +#!/usr/bin/env npx tsx +/** + * Standalone org chart image generator. + * + * Renders each of the 5 org chart styles to PNG using Playwright (headless Chromium). + * This gives us browser-native emoji rendering, full CSS support, and pixel-perfect output. + * + * Usage: + * npx tsx scripts/generate-org-chart-images.ts + * + * Output: tmp/org-chart-images/ + +
+${tree} +${PAPERCLIP_WATERMARK} +
+`; +} + +// ── Main ─────────────────────────────────────────────────────── + +async function main() { + const outDir = path.resolve("tmp/org-chart-images"); + fs.mkdirSync(outDir, { recursive: true }); + + const browser = await chromium.launch(); + const context = await browser.newContext({ + deviceScaleFactor: 2, // retina quality + }); + + const sizes = ["sm", "med", "lg"] as const; + const results: string[] = []; + + for (const style of STYLES) { + // README sizes + for (const size of sizes) { + const page = await context.newPage(); + const html = buildHtml(style, ORGS[size], false); + await page.setContent(html, { waitUntil: "networkidle" }); + + // Wait for fonts to load + await page.waitForFunction(() => document.fonts.ready); + await page.waitForTimeout(300); + + // Fit to content + const box = await page.evaluate(() => { + const el = document.querySelector(".org-tree")!; + const rect = el.getBoundingClientRect(); + return { + width: Math.ceil(rect.width) + 32, + height: Math.ceil(rect.height) + 32, + }; + }); + + await page.setViewportSize({ + width: Math.max(box.width, 400), + height: Math.max(box.height, 300), + }); + + const filename = `${style.key}-${size}.png`; + await page.screenshot({ + path: path.join(outDir, filename), + clip: { + x: 0, + y: 0, + width: Math.max(box.width, 400), + height: Math.max(box.height, 300), + }, + }); + await page.close(); + results.push(filename); + console.log(` ✓ ${filename}`); + } + + // OG card (1200×630) + { + const page = await context.newPage(); + await page.setViewportSize({ width: 1200, height: 630 }); + const html = buildHtml(style, OG_ORG, true); + // For OG, center the tree in a fixed viewport + const ogHtml = html.replace( + "", + ``, + ); + await page.setContent(ogHtml, { waitUntil: "networkidle" }); + await page.waitForFunction(() => document.fonts.ready); + await page.waitForTimeout(300); + + const filename = `${style.key}-og.png`; + await page.screenshot({ + path: path.join(outDir, filename), + clip: { x: 0, y: 0, width: 1200, height: 630 }, + }); + await page.close(); + results.push(filename); + console.log(` ✓ ${filename}`); + } + } + + await browser.close(); + + // Build an HTML comparison page + let compHtml = ` + + +Org Chart Style Comparison + + +

Org Chart Export — Style Comparison

+

5 styles × 3 org sizes + OG cards. All rendered via Playwright (browser-native emojis, full CSS).

+`; + + for (const style of STYLES) { + compHtml += `
+

${style.name}

+
README — Small / Medium / Large
+
+ + + +
+
OG Card (1200×630)
+
+
`; + } + + compHtml += ``; + fs.writeFileSync(path.join(outDir, "comparison.html"), compHtml); + console.log(`\n✓ All done! ${results.length} images generated.`); + console.log(` Open: tmp/org-chart-images/comparison.html`); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +});