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
76 lines
4.0 KiB
Markdown
76 lines
4.0 KiB
Markdown
---
|
|
name: html-to-pdf
|
|
description: 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: sticky` elements often just vanish or mis-render under the print media type.
|
|
- If the page has its own `@media print` rules (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
|
|
|
|
```bash
|
|
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
|
|
```
|
|
|
|
```js
|
|
// 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); });
|
|
```
|
|
|
|
```bash
|
|
node render.js
|
|
```
|
|
|
|
## Notes
|
|
|
|
- `printBackground: true` is 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'` on `goto` covers most cases; add an explicit `page.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.
|