Distills patterns from the Teleport access topology work: verified-data diagram building, as-displayed HTML-to-PDF export via headless Chromium, and container-scoped/host/database onboarding into an existing Teleport cluster. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3ZTfgQrkR3q8DSEoZAmvs
4.0 KiB
4.0 KiB
name, description
| name | description |
|---|---|
| html-to-pdf | Export a local HTML file to a PDF that looks exactly like the live browser rendering — no shrink-to-fit distortion, no forced page splits, no vanished sticky/grid elements. Use when the user says a browser's own Print-to-PDF/Print dialog produced a broken, tiny, or wrongly-paginated result and they want the PDF "as-is / seperti yang tampil di browser" instead of a print-reflowed version. |
HTML → PDF, exactly as displayed
Why the browser's own Print dialog fails for this
A system print dialog always targets a fixed paper size (Letter/A4). For any page wider than ~800px of real content — a multi-column CSS Grid layout, a wide diagram — the browser either shrinks the entire page to fit one sheet (readable content becomes a postage stamp) or splits it across pages in ways that don't match what's on screen. Two extra failure modes compound this:
position: stickyelements often just vanish or mis-render under the print media type.- If the page has its own
@media printrules (e.g. for a deliberately paginated print layout), those override the normal on-screen styling — which is the opposite of what "as-is" means here.
Neither "no print CSS at all" nor "add print CSS to force page-breaks" gives you the live browser view as a PDF. The actual fix is to render with a page size equal to the content's own dimensions, which no manual print dialog lets you set.
The fix: headless Chromium in screen mode, content-sized page
mkdir -p /tmp/pdfgen && cd /tmp/pdfgen
npm init -y >/dev/null 2>&1
npm install puppeteer # bundles a compatible Chromium — no separate browser install needed
// render.js
const puppeteer = require('puppeteer');
const path = require('path');
(async () => {
const srcPath = '/absolute/path/to/source.html';
const outPath = '/absolute/path/to/output.pdf';
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Match the viewport width the page's CSS was actually designed around
// (check its max-width / .page container, not an arbitrary guess).
await page.setViewport({ width: 1940, height: 1200, deviceScaleFactor: 2 });
await page.goto('file://' + srcPath, { waitUntil: 'networkidle0' });
// The critical line: force normal on-screen styles, ignoring any
// @media print rules the page might define for its own purposes.
await page.emulateMediaType('screen');
// Measure the real rendered size so the PDF page is exactly that size —
// one continuous page, nothing scaled, nothing split.
const { width, height } = await page.evaluate(() => {
const el = document.querySelector('.page') || document.body;
const rect = el.getBoundingClientRect();
return { width: Math.ceil(rect.width), height: Math.ceil(document.documentElement.scrollHeight) };
});
await page.pdf({
path: outPath,
width: `${width}px`,
height: `${height}px`,
printBackground: true, // otherwise CSS background colors silently disappear
margin: { top: 0, right: 0, bottom: 0, left: 0 },
});
await browser.close();
console.log('Saved:', outPath, width, 'x', height);
})().catch(err => { console.error(err); process.exit(1); });
node render.js
Notes
printBackground: trueis easy to forget and its absence is easy to misdiagnose as "the PDF looks washed out" rather than "backgrounds are just missing."- If the page measures its own scroll height before web fonts finish loading, dimensions can be slightly off —
waitUntil: 'networkidle0'ongotocovers most cases; add an explicitpage.evaluateHandle('document.fonts.ready')await first if fonts still look unloaded in the output. - This same technique works for any "I want the PDF to look like the page, not like a printout" request — it's not specific to any one diagram or document.
- Verify the result before calling it done: read the generated PDF back (most tools can render/preview a PDF's first page) and compare it against the live browser view side by side, don't just trust that
page.pdf()succeeded without error.