The font is not in the image
A slim base image ships almost no fonts. Chromium substitutes silently, so the invoice renders — with different metrics, different line wrapping and, for some currency and non-Latin glyphs, empty boxes. Nothing errors.
An invoice is a document a customer keeps and an auditor may read years later. It is generated from data your application already owns, so there is no page to point a renderer at — and the same invoice regenerated next month must not look different.
Totals drift by a decimal between the screen and the PDF, a currency symbol renders as a box, the layout that fit one page on your laptop spills onto two in production, or the amount is right and the thousands separator belongs to another locale.
Build the markup from the invoice record, set it directly on the page so nothing has to be hosted, and print with explicit paper and margins.
import { chromium } from "playwright";
// Formate no seu código, com locale explícito. O default do sistema muda
// entre a sua máquina e o container e ninguém percebe até o cliente reclamar.
const total = new Intl.NumberFormat("pt-BR", {
style: "currency",
currency: "BRL",
}).format(invoice.totalCents / 100);
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setContent(renderInvoiceHtml({ ...invoice, total }), { waitUntil: "load" });
await page.evaluateHandle(() => document.fonts.ready);
await page.pdf({
path: "invoice.pdf",
format: "A4",
printBackground: true,
margin: { top: "0.6in", bottom: "0.6in", left: "0.5in", right: "0.5in" },
});
await browser.close();A slim base image ships almost no fonts. Chromium substitutes silently, so the invoice renders — with different metrics, different line wrapping and, for some currency and non-Latin glyphs, empty boxes. Nothing errors.
It resolves for fonts the document requested. A face requested by CSS that never loads still resolves the promise, and the fallback is what gets printed.
A Chromium upgrade can change text shaping and pagination. For a document a customer keeps, 'renders correctly today' is weaker than it sounds — you need the archived bytes or a pinned engine.
If the invoice is one stable layout with no browser CSS requirement, a native PDF library is smaller, faster and more predictable than a browser. This is the case where DIY genuinely wins.
A PDF whose text is selectable and searchable — an auditor can copy the total out of it. The engine version that produced it is recorded on the request, so a document can be traced back to how it was rendered.
One successful, non-cached capture costs one credit. Failures, including platform failures, cost zero; cache hits cost zero.
`html` was combined with `url`, or a PDF option was sent with a non-PDF format.
HTTP 400 · terminal
A remote font or image in the markup never resolves and consumes the job budget.
HTTP 500 · retryable
Images were inlined as data URIs instead of referenced by URL.
HTTP 413 · terminal
An asset in the markup points at a private or unreachable host.
HTTP 500 · retryable
The runner above carries this exact configuration into the playground — no retyping, no starting over.
Written by PageCapture Engineering. Reviewed by PageCapture PDF maintainers. Verified 2026-08-12 by running the published code against a fixture.
The published cURL, TypeScript and Python are executed against the inline-HTML path in CI and the artifact is checked to be a real PDF; the request is normalized by the same contract as the public API.
Sources support the browser and API behaviors named above. PageCapture-specific limits and billing are taken from the public contract; external sources are used for the underlying browser behavior.
Defines explicit locale and currency formatting, rather than the host default.
Defines page-oriented layout for printed documents.
Canonical parameter names, paper formats and margin limits.
The commercial page for this exact job, with pricing per workload.
Add page numbers once a single invoice spans several sheets.
The font-readiness failure in detail, with the same root cause.
Exact paper formats, margins and template fields.