Skip to content
Future Site
All news
Feature

Pixel-exact A4 PDFs from HTML with Gotenberg

3 min read

The CVolve templates page with three CV designs: Classic, Modern and Software Engineer

A CV builder lives or dies by its PDF export. If the PDF looks different from the preview, if a heading moves to the next page or a font disappears, people stop trusting the tool.

In CVolve and CVolve Template Maker, templates are written in HTML and CSS. To turn them into PDFs that match the preview exactly, we use Gotenberg.

Why not a PHP PDF library?

PHP has PDF libraries that convert HTML, but they implement their own subset of CSS. Modern layouts with Flexbox, Grid, custom fonts and print rules either render differently or do not render at all. Template designers would have to learn which parts of CSS "work in the PDF", and that is not a good experience.

A real browser does not have this problem. Chromium already understands modern CSS and can print to PDF. What we needed was a clean way to use Chromium from a PHP application.

What Gotenberg is

Gotenberg is an open-source, Docker-based API for converting documents. It runs Chromium (and LibreOffice) behind simple HTTP endpoints. You send HTML as a multipart form, and you get a PDF back. In our docker-compose.yml it is one service:

gotenberg:
  image: gotenberg/gotenberg:8

The PHP app does not need Chromium, Node.js or any browser libraries installed. It only needs an HTTP client.

A4, exactly

Our GotenbergClient posts the rendered CV to Chromium's HTML route with print settings for A4:

$this->post('/forms/chromium/convert/html', $html, [
    'paperWidth'        => '8.27',   // inches = 210 mm
    'paperHeight'       => '11.7',   // inches = 297 mm
    'marginTop'         => '0',
    'marginBottom'      => '0',
    'marginLeft'        => '0',
    'marginRight'       => '0',
    'printBackground'   => 'true',
    'preferCssPageSize' => 'true',
    'emulatedMediaType' => 'print',
]);

Margins are zero because the template owns the design, including its margins, through CSS @page rules. preferCssPageSize lets a template declare its own page size, and printBackground keeps coloured sidebars and header bands.

More than PDFs

The same container does three other jobs:

  • Thumbnails. The screenshot route renders the first page as a PNG at 794 × 1123 pixels (A4 at 96 DPI) for the template gallery.
  • Page counting. CVolve's checker warns you when a CV is longer than the template allows. It renders the PDF and counts its pages, so the number is the real one, not an estimate.
  • Exact preview. The live editor preview is fast and shows red guides where pages end. When you want the final result, the PDF preview shows exactly what Gotenberg will export.

For recruiters who want Word, CVolve also exports editable DOCX with PHPWord. The PDF is the reference design, and the DOCX is the editable copy.

Locking Chromium down

Templates can come from anywhere: a designer, a JSON Resume theme, or an AI. A browser that renders untrusted HTML must not be allowed to fetch remote URLs. Otherwise a template could load tracking pixels or try to reach internal services.

We start Gotenberg with a strict deny list:

command: ["gotenberg", "--chromium-deny-list=^(?!data:|file:///tmp/).*", "--api-timeout=60s"]

Chromium may only load data: URLs and the files Gotenberg itself writes to /tmp. Everything else is blocked. This is also why template fonts are embedded in the export instead of linked from a CDN: the PDF is complete, private and reproducible.

Takeaway

If your documents are designed in HTML and CSS, render them with a real browser. Gotenberg puts Chromium behind a small, stateless HTTP API that fits easily into a Docker setup. Lock the browser down, let templates control their own page rules, and your PDFs will match your previews.

CVolve is open source under the MIT License. The export code is in src/Service/Export/ if you want to see the whole thing.