ScreenshotNeo

BlogHTML to image & PDF

How to Fix CSS calc() Not Working in wkhtmltopdf 0.12.4

wkhtmltopdf 0.12.4 uses an outdated WebKit. Replace calc() with build-time values, add fallbacks, or migrate to a newer renderer.

By the ScreenshotNeo team1 October 20266 min read

Direct answer: CSS calc() often fails in wkhtmltopdf 0.12.4 because it embeds an old Qt WebKit engine. The wkhtmltopdf project says its WebKit has not been updated since 2012, and the upstream issue tracker documents CSS3 problems including calc(). Move the arithmetic into your build step and emit ordinary CSS such as width: 20%. If one stylesheet must serve both browsers and wkhtmltopdf, put a fixed fallback before the calc() declaration.

Use a build-time value instead of calc()

For a five-column layout, calculate the value before rendering:

$column-width: 20%;

.card {
  width: $column-width;
}

After compiling SCSS, wkhtmltopdf receives CSS it can parse:

.card {
  width: 20%;
}

You can also calculate the value in application code and write the resulting number into a template:

<div class="card" style="width: 20%;">Card</div>

Keep a browser version and a PDF fallback

When the same stylesheet is used by Chrome and wkhtmltopdf, declare the compatible value first:

.card {
  width: 20%;
  width: calc(100% / 5);
}

A renderer that ignores the second declaration keeps 20%. This fallback does not make calc() work in wkhtmltopdf; it simply gives the old renderer a value it understands.

Why -webkit-calc() usually does not help

Changing the spelling to -webkit-calc() can be a quick diagnostic, but a vendor prefix cannot add a missing parser or evaluator to the embedded WebKit. If the engine does not implement the feature, both forms can fail:

.card {
  width: 20%;
  width: -webkit-calc(100% / 5);
  width: calc(100% / 5);
}

Use ordinary lengths, percentages, margins and padding when targeting 0.12.4. Do not depend on newer CSS features merely because a current browser accepts them.

Diagnose the failure step by step

  1. Confirm the binary.
    wkhtmltopdf --version

    Record the complete version and operating system. The project lists 0.12.4 as an archived release from November 22, 2016.

  2. Reduce the page to one declaration.
    <!doctype html>
    <html>
    <head>
      <style>
        .test { width: calc(100% / 5); height: 40px; background: red; }
      </style>
    </head>
    <body>
      <div class="test"></div>
    </body>
    </html>

    If this minimal case fails, the problem is engine support rather than your application layout.

  3. Check that the stylesheet loads. Temporarily add an obvious fixed style such as background: red or width: 100px. If that style is missing, fix the URL, file permission, or asset-loading problem first.
  4. Compare with a fallback.
    .test { width: 20%; width: calc(100% / 5); }

    If the fallback renders correctly, the CSS engine is ignoring calc().

  5. Test an inline stylesheet. This separates external stylesheet loading from CSS support.
  6. Try a user stylesheet when appropriate.
    wkhtmltopdf --user-style-sheet overrides.css input.html output.pdf

    The option helps verify that an override is being applied; it does not add unsupported CSS features.

Complete command-line example

Save this as input.html:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <style>
    .grid { width: 100%; font-size: 0; }
    .card {
      display: inline-block;
      vertical-align: top;
      width: 20%;
      width: calc(100% / 5);
      min-height: 80px;
      box-sizing: border-box;
      padding: 8px;
      font-size: 14px;
      border: 1px solid #ccc;
    }
  </style>
</head>
<body>
  <div class="grid">
    <div class="card">One</div>
    <div class="card">Two</div>
    <div class="card">Three</div>
    <div class="card">Four</div>
    <div class="card">Five</div>
  </div>
</body>
</html>

Generate the PDF:

wkhtmltopdf --enable-local-file-access input.html output.pdf

If your page depends on JavaScript, you can allow a short delay or run a script, but those options do not change CSS support:

wkhtmltopdf --javascript-delay 500 --run-script "window.status='ready'" input.html output.pdf

Options that help isolate layout problems

Option or check Use Limitation
--user-style-sheet Apply a known override stylesheet. Cannot add calc() support.
--viewport-size WIDTHxHEIGHT Reproduce a browser viewport for responsive rules. Does not upgrade WebKit.
--run-script Execute a small diagnostic script. JavaScript cannot repair an unsupported CSS parser.
Smart-shrinking controls Investigate apparent size changes caused by page scaling. Scaling can change dimensions but does not evaluate calc().

Common errors and fixes

Symptom Likely cause Fix
The element has zero or unexpected width. calc() was ignored. Emit a percentage or pixel fallback before it.
Both fallback and calc() fail. The stylesheet is not loaded. Test a fixed color or width, then check paths, permissions and local-file access.
-webkit-calc() changes nothing. The old engine lacks the implementation. Move arithmetic to SCSS, a template, or application code.
Layout differs only in the PDF. Different viewport, font metrics, page width or WebKit behavior. Set the viewport explicitly, use stable units, embed required fonts, and compare a minimal case.
CSS works in Chrome but not 0.12.4. Chrome has a newer rendering engine. Target the lowest common feature set or migrate the PDF renderer.
JavaScript-created styles are missing. Rendering occurs before the page is ready. Use a readiness signal or delay, then verify the generated CSS is supported.

When upgrading is the better fix

The official downloads information identifies 0.12.6 as the stable series released June 11, 2020, while 0.12.4 is archived. Upgrading may remove some limitations, but you should still verify your exact CSS and pagination output. The project status guidance recommends evaluating WeasyPrint or Prince for controlled, static report generation and Puppeteer for pages that depend on dynamic JavaScript.

Renderer Evaluate it when Questions to answer
WeasyPrint The document is mostly static and CSS-focused. Does it support the layout and pagination rules you use?
Prince You need controlled report output and can accept its deployment and licensing model. Does its CSS and print feature set match your templates?
Puppeteer The page is a live JavaScript application. Can your environment run Chromium reliably and at the required scale?

Compare candidates on CSS feature coverage, JavaScript execution, pagination and print fidelity, and operational cost or deployment complexity. Include representative pages with fonts, images, tables and page breaks in the comparison.

Performance, reliability and cost considerations

  • Build-time arithmetic is cheap and deterministic. It avoids asking the renderer to evaluate expressions for every element.
  • Minimal HTML/CSS cases render faster and make failures reproducible. Keep a regression fixture for each layout pattern.
  • Pin the wkhtmltopdf binary and operating-system image so upgrades do not silently change pagination.
  • Cache compiled CSS, but invalidate it whenever column counts, page sizes or template variables change.
  • For high-volume PDF jobs, measure queue time, rendering time, memory use and failure rates with your own documents. The supplied research contains no independent benchmark to generalize.

Or skip the browser setup

If your actual goal is a clean screenshot or PDF of a web page rather than maintaining wkhtmltopdf, ScreenshotNeo provides a single GET request. Its capture pipeline accepts cookie and consent banners before the shot and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also provides an MCP server for Claude, Cursor and other MCP clients.

See the ScreenshotNeo API documentation for all options.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);

ScreenshotNeo includes full-page capture with lazy images loaded, element capture by CSS selector, dark mode, device presets and custom viewports, retina scale, PDF paper size and margins, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs, webhooks, bulk capture and a usage API.

Start with 1,000 screenshots per month free, with no card required. Paid plans start at $5 for 3,000 screenshots.

FAQ

Does adding spaces around the operators fix calc()?

No. Spacing is required by some CSS parsers in certain expressions, but it cannot add support to wkhtmltopdf’s old engine. Use a generated value and fallback.

Can JavaScript calculate the width instead?

Yes. Compute the value in application code or JavaScript and assign a plain percentage or pixel width before conversion. Confirm that the script has finished before wkhtmltopdf captures the page.

Should I report the bug upstream?

Include the wkhtmltopdf version, operating system and a minimal HTML/CSS/JavaScript case, as requested by the project. Archived releases may not accept new bug reports.

Is 0.12.6 guaranteed to support every CSS expression?

No. Test the exact expressions and print layouts your application needs. A newer release can still differ from a current browser.