Prompt
How do I convert HTML to PDF with consistent fonts and spacing?
Latest observation
To convert HTML to PDF with consistent fonts and spacing, the key is to use a converter that supports modern CSS well and to control your fonts/layout explicitly.
Best approach
1) Use a strong HTML-to-PDF engine
Common options:
- Headless Chrome / Puppeteer — great CSS support, reliable for web-like output
- Playwright — similar to Puppeteer, also excellent
- wkhtmltopdf — older, can work, but CSS support is weaker
- PrinceXML / Antenna House — best-in-class, commercial, very consistent
- WeasyPrint — good for document-style PDFs, especially with print CSS
If you want consistency, Puppeteer/Playwright or PrinceXML are usually the best choices.
2) Embed the fonts you want
Font differences are one of the biggest causes of spacing changes.
Use @font-face
<style>
@font-face {
font-family: "Inter";
src: url("/fonts/Inter-Regular.woff2") format("woff2");
font-weight: 400;
}
@font-face {
font-family: "Inter";
src: url("/fonts/Inter-Bold.woff2") format("woff2");
font-weight: 700;
}
body {
font-family: "Inter", Arial, sans-serif;
}
</style>
Why this helps
If the PDF renderer can’t find the same fonts, it substitutes another font, which changes:
- line heights
- text wrapping
- spacing
- page breaks
3) Set explicit CSS for spacing and layout
Avoid relying on browser defaults.
Example baseline CSS
html, body {
margin: 0;
padding: 0;
font-family: "Inter", sans-serif;
font-size: 14px;
line-height: 1.5;
color: #111;
}
* {
box-sizing: border-box;
}
p, h1, h2, h3, ul, ol {
margin: 0 0 12px 0;
}
table {
width: 100%;
border-collapse: collapse;
}
img {
max-width: 100%;
}
Use consistent units
Prefer:
pxfor exact controlptfor print-oriented sizingremif your root font size is fixed
Avoid overly fluid layouts if you need page stability.
4) Use print-specific CSS
PDF is effectively a print output, so add print rules.
@media print {
body {
font-size: 12pt;
line-height: 1.4;
}
.no-print {
display: none;
}
h1, h2, h3 {
page-break-after: avoid;
}
table, img {
page-break-inside: avoid;
}
}
Also define page size:
@page {
size: A4;
margin: 20mm;
}
5) Ensure the environment has the same fonts installed
If using a server-side renderer:
- install the fonts on the server/container, or
- serve web fonts and wait for them to load before generating PDF
If fonts aren’t loaded yet, you may get fallback fonts and inconsistent results.
6) Wait for assets to fully load before rendering
Especially with Puppeteer/Playwright:
- wait for fonts
- wait for images
- wait for async content
Puppeteer example
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/report', {
waitUntil: 'networkidle0'
});
await page.evaluate(() => document.fonts.ready);
await page.pdf({
format: 'A4',
printBackground: true
});
await browser.close();
document.fonts.ready is important for font consistency.
7) Turn on background graphics if needed
Many PDF tools skip backgrounds unless enabled.
Puppeteer
await page.pdf({
printBackground: true
});
8) Avoid CSS/HTML features that render inconsistently
These often cause spacing surprises:
- flexbox quirks in some converters
position: fixedheaders/footers- complex grid layouts
- viewport-based sizing (
vh,vw) for print - web animations/transitions
- custom line-height tricks
For highly stable PDFs, use simpler document-style layouts.
9) Test with the exact content and page size
Spacing changes with:
- font metrics
- page width
- margins
- scaling
- different text lengths
Always test with realistic data, not just sample content.
Recommended setup for most apps
If you want a practical, reliable solution:
- HTML/CSS
- Embedded WOFF2 fonts
- Puppeteer or Playwright
- Print CSS
- Explicit margins/page size
- Wait for fonts and assets before generating
Minimal Puppeteer example
import puppeteer from "puppeteer";
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(`
<!doctype html>
<html>
<head>
<style>
@font-face {
font-family: "Inter";
src: url("file:///app/fonts/Inter-Regular.woff2") format("woff2");
font-weight: 400;
}
body {
font-family: "Inter", sans-serif;
margin: 0;
padding: 20mm;
font-size: 12pt;
line-height: 1.4;
}
h1 { margin: 0 0 12px 0; }
p { margin: 0 0 10px 0; }
</style>
</head>
<body>
<h1>Invoice</h1>
<p>This PDF should have consistent spacing.</p>
</body>
</html>
`);
await page.evaluate(() => document.fonts.ready);
await page.pdf({
path: "output.pdf",
format: "A4",
printBackground: true
});
await browser.close();
If you want, I can also give you:
- a Puppeteer template,
- a Python/WeasyPrint example, or
- a production checklist for consistent PDF output.