Developers using wkhtmltopdf frequently encounter a frustrating issue: text that appears correctly in the browser renders as empty boxes, squares, or question marks in the generated PDF. This problem has plagued wkhtmltopdf deployments for years, particularly on Linux servers and Docker containers where font configuration differs from development machines. With wkhtmltopdf now officially archived and abandoned, these font rendering issues will never receive upstream fixes.
The Problem
When wkhtmltopdf converts HTML to PDF, it relies on the Qt WebKit rendering engine to process fonts. Unlike modern browsers that include comprehensive font fallback mechanisms and embedded font support, wkhtmltopdf depends entirely on fonts installed on the host system. If a character cannot be rendered using an available font, wkhtmltopdf displays a "tofu" character: the empty rectangle box that indicates a missing glyph.
The problem manifests in several ways depending on the specific cause:
- Characters render as empty rectangles or boxes (the classic "tofu" symbol)
- Text displays as question marks or diamond question mark symbols
- Some characters appear while others are missing entirely
- PDF output differs between local development and production servers
- Unicode characters like CJK (Chinese, Japanese, Korean), Hebrew, Arabic, or Greek scripts fail to render
- Custom fonts specified via
@font-faceare ignored or substituted
The issue is architectural: wkhtmltopdf uses an outdated Qt WebKit engine that lacks the sophisticated font handling found in modern Chromium-based browsers. The project was archived in January 2023 and is no longer maintained, meaning these limitations are permanent.
Error Symptoms
There is no error message when fonts fail to render. The PDF generates successfully but contains visual defects:
Expected output: "Hello, World! 你好世界"
Actual output: "Hello, World! □□□□"
Expected output: "Invoice #12345 - €500.00"
Actual output: "Invoice #12345 - ?500.00"
Common symptoms include:
- Empty boxes (tofu) in place of Unicode characters
- Entire lines of text missing or replaced with rectangles
- Correct rendering on Windows but boxes on Linux
- PDF works locally but fails on production server
- Docker containers show boxes where fonts render on host machine
- Non-Latin characters (Chinese, Arabic, Hebrew, Greek) appear as squares
-
@font-facecustom fonts ignored, replaced with system default
Who Is Affected
This issue impacts any deployment of wkhtmltopdf where fonts are not explicitly installed and configured on the rendering system.
Operating Systems: Linux servers (Ubuntu, Debian, CentOS, Alpine), Docker containers, and cloud platforms where minimal base images lack font packages. Windows servers are less affected because they include more fonts by default.
Framework Versions: All wkhtmltopdf versions including 0.12.6 (the final release). .NET wrappers including DinkToPdf, Rotativa, and NReco.PdfGenerator inherit this limitation.
Use Cases: Invoice generation with currency symbols, multilingual document generation, reports containing special characters, applications serving international users, any HTML-to-PDF workflow with custom fonts or non-ASCII text.
Environments: Particularly problematic in containerized deployments (Docker, Kubernetes), serverless functions (AWS Lambda), and minimal Linux server installations where font packages are not included by default.
Evidence from the Developer Community
Font rendering issues are among the most commonly reported problems in the wkhtmltopdf issue tracker, with hundreds of related issues spanning nearly a decade.
Timeline
| Date | Event | Source |
|---|---|---|
| 2015-04-01 | Font-face rendering inconsistencies reported | GitHub Issues |
| 2016-09-01 | Multiple @font-face rules causing failures | GitHub Issues |
| 2017-06-01 | UTF-8 encoding issues on Linux documented | GitHub Issues |
| 2018-03-01 | Chinese characters rendering as squares | GitHub Issues |
| 2019-11-01 | Platform differences in font rendering confirmed | GitHub Issues |
| 2020-05-01 | Alpine Linux showing no characters at all | GitHub Issues |
| 2021-08-01 | Font-family ignored in 0.12.x releases | GitHub Issues |
| 2023-01-02 | Repository archived, read-only | GitHub |
| 2024-12-16 | Homebrew disables wkhtmltopdf cask | Homebrew |
Community Reports
"When running wkhtmltopdf on CentOS 7, UTF-8 characters are either missing (empty fields) or displayed as squares."
— Developer, GitHub Issues, 2017"I use the embedded fonts in css with wkhtmltopdf to print website to PDF, it works perfectly under mac, but when running on Ubuntu with the same version of wkhtmltopdf, the pdf is slightly different."
— Developer, GitHub Issue #2884"On Alpine Linux with wkhtmltopdf 0.12.6, no characters are rendered. I only see squares."
— Developer, GitHub Issue #4836"ttf-dejavu, ttf-droid, ttf-freefont, ttf-liberation are all font packages needed by wkhtmltopdf to render the pdf properly, and you might experience black boxes instead of letters if you are missing some."
— Developer, Setting up WKHTMLTOPDF on Docker Alpine Linux
CiviCRM issued a formal security advisory (CIVI-PSA-2024-01) noting that wkhtmltopdf has reached end-of-life and carries unpatched security vulnerabilities including CVE-2020-21365 and CVE-2022-35583.
Root Cause Analysis
The font rendering problem in wkhtmltopdf stems from multiple architectural limitations.
Outdated Rendering Engine: wkhtmltopdf uses Qt WebKit, an engine that was abandoned by Qt in 2016 in favor of Qt WebEngine (Chromium-based). The WebKit engine lacks modern font rendering capabilities including proper Unicode fallback chains, variable font support, and robust @font-face handling.
System Font Dependency: Unlike Chromium-based renderers that include font fallback mechanisms, wkhtmltopdf requires fonts to be installed at the operating system level. It cannot fallback to other fonts when a glyph is missing from the specified font.
No Embedded Font Subsetting: When using @font-face, wkhtmltopdf has limited support for web fonts. It does not handle WOFF2 format well, has issues with multiple font-weight variants sharing the same family name, and requires fonts to be accessible via absolute file paths in many scenarios.
Platform Inconsistency: The Qt WebKit engine behaves differently across operating systems. The same HTML may render correctly on macOS (which includes many Unicode fonts) while failing on Linux servers (which often include only basic fonts).
fontconfig Conflicts: On Linux, the fontconfig system can interfere with font selection. Hinting settings, font substitution rules, and cache issues can cause wkhtmltopdf to select incorrect fonts or fail to find fonts that are installed.
The root cause is fundamental: wkhtmltopdf is built on abandoned technology that will never receive improvements.
Attempted Workarounds
Developers have documented various approaches to mitigate font rendering issues.
Workaround 1: Install Font Packages
Approach: Install comprehensive font packages on the Linux server or Docker container.
# Dockerfile for Ubuntu/Debian
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
wkhtmltopdf \
fonts-dejavu \
fonts-liberation \
fonts-noto \
fonts-noto-cjk \
fonts-noto-color-emoji \
fontconfig \
&& fc-cache -fv
# Dockerfile for Alpine Linux
FROM alpine:3.18
RUN apk add --no-cache \
wkhtmltopdf \
ttf-dejavu \
ttf-droid \
ttf-freefont \
ttf-liberation \
font-noto \
font-noto-cjk \
fontconfig \
&& fc-cache -f
Limitations:
- Increases container image size significantly (Noto CJK fonts alone are over 100MB)
- Must know in advance which languages will be rendered
- Does not solve
@font-facecustom font issues - Does not address security vulnerabilities in wkhtmltopdf itself
Workaround 2: Embed Fonts as Base64
Approach: Embed fonts directly in the HTML using base64-encoded TTF files.
@font-face {
font-family: 'CustomFont';
src: url('data:font/truetype;charset=utf-8;base64,AAEAAAALAIAAAwAwT1...') format('truetype');
font-weight: normal;
font-style: normal;
}
Limitations:
- Only TTF format works reliably; WOFF and WOFF2 have compatibility issues
- Font family names must be 8 characters or fewer in some cases
- Cannot use multiple weights (bold, light) with the same family name
- Significantly increases HTML file size
- Complex to implement in templates
Workaround 3: Use Absolute Font Paths
Approach: Reference fonts using absolute file system paths or localhost URLs.
@font-face {
font-family: 'MyFont';
src: url('file:///usr/share/fonts/truetype/myfont.ttf') format('truetype');
}
@font-face {
font-family: 'MyFont';
src: url('http://localhost/fonts/myfont.ttf') format('truetype');
}
Limitations:
- Requires fonts to be installed at specific paths
- Localhost URLs require a running web server
- Deployment complexity increases
- Path differences between development and production environments
Workaround 4: Add Charset Meta Tag
Approach: Ensure UTF-8 encoding is properly declared.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<!-- Content -->
</body>
</html>
Limitations:
- Only addresses encoding issues, not missing font glyphs
- Does not help when fonts are simply not installed
- Necessary but not sufficient for proper rendering
Workaround 5: Disable Font Hinting
Approach: Configure fontconfig to disable hinting, which can cause kerning and rendering artifacts.
<!-- /etc/fonts/local.conf -->
<fontconfig>
<match target="font">
<edit name="hinting" mode="assign">
<bool>false</bool>
</edit>
</match>
</fontconfig>
fc-cache -fv
Limitations:
- Affects all applications on the system
- May not resolve missing glyph issues
- Requires system-level configuration access
A Different Approach: IronPDF
For applications where font rendering reliability is critical, switching from wkhtmltopdf to a Chromium-based renderer eliminates the underlying font handling limitations. IronPDF embeds the Chromium browser engine, providing the same font rendering behavior developers see in Chrome, including automatic font fallback and comprehensive Unicode support.
Why IronPDF Avoids This Issue
IronPDF's architecture differs fundamentally from wkhtmltopdf. Rather than using the abandoned Qt WebKit engine, IronPDF embeds a Chromium browser that includes:
- Automatic font fallback chains when glyphs are missing
- Native support for WOFF, WOFF2, and variable fonts
- Consistent rendering across Windows, Linux, and macOS
- Full
@font-facesupport including multiple weights and styles - Built-in Unicode support without requiring system font installation for most scripts
When a font lacks a particular glyph, Chromium automatically falls back to other available fonts rather than displaying a box. This mirrors how Chrome handles font rendering in the browser.
Code Example
The following example demonstrates HTML-to-PDF conversion with custom fonts and Unicode text, scenarios where wkhtmltopdf typically fails:
using IronPdf;
using System;
public class FontRenderingExample
{
public void GeneratePdfWithCustomFonts()
{
// IronPDF handles font rendering through embedded Chromium
// No system font configuration required
var renderer = new ChromePdfRenderer();
// HTML with custom web fonts and Unicode characters
string html = @"
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<style>
/* Google Fonts work seamlessly */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC&display=swap');
body {
font-family: 'Roboto', sans-serif;
}
.chinese {
font-family: 'Noto Sans SC', sans-serif;
}
.arabic {
font-family: 'Noto Sans Arabic', Arial, sans-serif;
direction: rtl;
}
.symbols {
font-size: 24px;
}
</style>
</head>
<body>
<h1>Multilingual Document</h1>
<h2>English with Custom Font</h2>
<p>This text uses the Roboto font from Google Fonts.</p>
<p><strong>Bold text renders correctly</strong> with font-weight: 700.</p>
<h2>Chinese Characters</h2>
<p class='chinese'>你好世界 - Chinese text renders properly.</p>
<p class='chinese'>简体中文和繁體中文都支持。</p>
<h2>Currency and Symbols</h2>
<p class='symbols'>€ £ ¥ $ ₹ ₽ ₿</p>
<p class='symbols'>© ® ™ § ¶ † ‡</p>
<h2>Emoji Support</h2>
<p>Documents can include emoji: ✓ ✗ ★ ☆ ♠ ♥ ♦ ♣</p>
<h2>Mathematical Symbols</h2>
<p>∑ ∏ ∫ √ ∞ ≠ ≤ ≥ ± × ÷</p>
</body>
</html>";
// Render HTML to PDF - fonts load automatically
using (var pdf = renderer.RenderHtmlAsPdf(html))
{
pdf.SaveAs("multilingual-document.pdf");
Console.WriteLine("PDF generated with all fonts rendered correctly.");
}
}
public void GeneratePdfWithEmbeddedFonts()
{
var renderer = new ChromePdfRenderer();
// Custom fonts can be embedded via @font-face
// WOFF2 format works reliably (unlike wkhtmltopdf)
string html = @"
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<style>
@font-face {
font-family: 'CustomBrand';
src: url('https://example.com/fonts/brand-regular.woff2') format('woff2'),
url('https://example.com/fonts/brand-regular.woff') format('woff');
font-weight: 400;
}
@font-face {
font-family: 'CustomBrand';
src: url('https://example.com/fonts/brand-bold.woff2') format('woff2');
font-weight: 700;
}
body {
font-family: 'CustomBrand', Arial, sans-serif;
}
h1 {
font-weight: 700;
}
</style>
</head>
<body>
<h1>Invoice #12345</h1>
<p>Total: <strong>€1,234.56</strong></p>
<p>Customer: François Müller</p>
<p>Address: 東京都渋谷区</p>
</body>
</html>";
using (var pdf = renderer.RenderHtmlAsPdf(html))
{
pdf.SaveAs("invoice-with-custom-fonts.pdf");
}
}
}
Key points about this code:
- Google Fonts and web fonts load automatically without system installation
- WOFF2 format works reliably, unlike wkhtmltopdf's TTF-only limitation
- Multiple font weights (400, 700) with the same family name function correctly
- Unicode characters render using Chromium's built-in font fallback
- No fontconfig configuration or font package installation required
- Consistent output across Windows, Linux, and macOS
Docker Deployment
IronPDF in Docker does not require extensive font package installation:
FROM mcr.microsoft.com/dotnet/aspnet:8.0
# IronPDF handles fonts internally through Chromium
# Minimal additional packages needed
RUN apt-get update && apt-get install -y \
libc6 \
libgcc1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "MyApp.dll"]
Compare this to wkhtmltopdf deployments requiring dozens of font packages totaling hundreds of megabytes.
API Reference
For details on the methods used above:
- ChromePdfRenderer - Primary rendering class using embedded Chromium
- Docker and Linux Deployment - Container configuration guide
- HTML to PDF Examples - Additional rendering scenarios
Migration Considerations
Licensing
IronPDF is commercial software with per-developer licensing. A free trial is available for evaluation. Teams should verify that IronPDF meets their requirements before committing to migration from wkhtmltopdf.
API Differences
Migrating from wkhtmltopdf wrappers (DinkToPdf, Rotativa, NReco) requires API changes:
| wkhtmltopdf (DinkToPdf) | IronPDF |
|---|---|
new SynchronizedConverter(...) |
new ChromePdfRenderer() |
converter.Convert(document) |
renderer.RenderHtmlAsPdf(html) |
| PDF saved via byte array |
pdf.SaveAs(path) or pdf.BinaryData
|
| Command-line flags |
renderer.RenderingOptions properties |
Migration effort varies based on codebase size and how deeply wkhtmltopdf is integrated.
What You Gain
- Fonts render correctly without extensive system configuration
- Unicode and multilingual text work out of the box
- Modern CSS support including Flexbox, Grid, and CSS3
- No security vulnerabilities from abandoned Qt WebKit
- Active maintenance and support
- Consistent rendering across platforms
What to Consider
- Commercial licensing cost versus free (abandoned) wkhtmltopdf
- Chromium engine has larger memory footprint than Qt WebKit
- Some wkhtmltopdf-specific command flags have no direct equivalent
- Learning curve for teams familiar with wkhtmltopdf configuration
Conclusion
The wkhtmltopdf fonts not rendering issue stems from architectural limitations in its abandoned Qt WebKit engine. With the project archived and no longer maintained, these problems will persist indefinitely for any application continuing to use wkhtmltopdf. For developers encountering boxes, squares, or missing characters in PDF output, migrating to a Chromium-based renderer like IronPDF eliminates the font handling problems at their source while also addressing the security concerns of running deprecated software.
Jacob Mellor leads technical development at Iron Software and originally built IronPDF.
References
- Figuring out missing fonts for wkHTMLtoPDF{:rel="nofollow"} - Detailed guide on diagnosing missing fonts
- Chinese Characters Missing - GitHub Issue #4879{:rel="nofollow"} - CJK character rendering problem
- No characters rendered - GitHub Issue #4836{:rel="nofollow"} - Alpine Linux showing only squares
- Font not rendering properly with Linux Binary - GitHub Issue #3585{:rel="nofollow"} - Platform differences in font rendering
- Font-face not render correctly with different platform - GitHub Issue #2884{:rel="nofollow"} - Cross-platform @font-face issues
- Multiple @font-face rules - GitHub Issue #2435{:rel="nofollow"} - Font-weight variant problems
- wkhtmltopdf --encoding utf-8 not work on ubuntu - GitHub Issue #3233{:rel="nofollow"} - UTF-8 encoding issues
- CIVI-PSA-2024-01: wkhtmltopdf (EOL){:rel="nofollow"} - Security advisory on wkhtmltopdf end-of-life
- wkhtmltopdf Status Page{:rel="nofollow"} - Official project status
- Setting up WKHTMLTOPDF on Docker Alpine Linux{:rel="nofollow"} - Docker font configuration guide
For IronPDF documentation and tutorials, visit ironpdf.com.
Top comments (0)