Free tools Windows power users keep installed
One-click scans. No signup required.
Empty spaces, square “tofu” boxes, or black blocks in a Python 3 pdfkit PDF usually mean that the wkhtmltopdf process cannot find a font containing the exact characters. The reliable fix is to identify the failing code points, verify font coverage, make that font available to the machine and user account that render the PDF, select it explicitly in HTML/CSS, and inspect a PDF produced by the same deployment environment. A browser preview alone is not proof that the PDF renderer has the same fallback fonts.
What pdfkit actually does
pdfkit is a Python wrapper. It builds a command and invokes the separate wkhtmltopdf executable; that Qt/WebKit renderer performs font lookup, fallback, shaping and drawing. Python options cannot manufacture a glyph that is absent from every font visible to the renderer.
Start by recording the executable and version used by the running application, not merely the one installed on your workstation:
import pdfkit
config = pdfkit.configuration() # or pass wkhtmltopdf= explicitly
print(config.wkhtmltopdf)
print(pdfkit.from_string('<p>test</p>', False, configuration=config))
Also run the binary as the same service user where possible:
#1 Best Overall
/path/to/wkhtmltopdf --version
Different operating systems, Qt builds and users can expose different fonts. Historical reports from CentOS 7 with wkhtmltopdf 0.12.3 and Windows 10 with 0.12.5 (patched Qt) illustrate why a successful browser preview can be misleading; those reports are diagnostic examples, not current support guarantees.
Identify the failure before changing fonts
- Capture the exact characters. Save a minimal string containing every failing letter, mark, emoji or symbol and note its script. If possible, record Unicode code points (for example, with Python’s
ord()). - Classify the symptom. A blank area can indicate a missing glyph or failed resource; a hollow square or black square is a replacement glyph; correctly shaped letters in the wrong order indicate shaping or bidirectional-text problems.
- Reduce the document. Test only the affected text in a tiny HTML file. This prevents images, JavaScript and unrelated CSS from hiding the font problem.
text = "PASTE THE FAILING TEXT HERE"
print([(ch, f"U+{ord(ch):04X}") for ch in text])
Check that a candidate font covers the exact glyphs
A font described as “Unicode” or one that renders another language may still lack a particular character, combining mark, presentation form or shaping table. Coverage must be checked for the exact code points and for the script’s shaping needs. Compare candidates on these axes:
- Coverage of every failing code point, including combining marks.
- Availability to the production renderer, not just to your desktop browser.
- A loading route that your wkhtmltopdf build can use (system installation or an explicit CSS resource).
- Compatibility with the deployed renderer’s script-shaping behavior and font format.
- License terms for installation and redistribution.
Use your operating system’s font inspection tools or a font editor to verify coverage. Do not treat a successful browser preview as the test: browsers may silently fall back to fonts such as Yu Gothic UI, Nirmala UI or SimSun that are absent from the PDF host.
Make the font visible to the rendering process
System installation
Install the selected font in the server, container or worker that runs wkhtmltopdf. A font installed only on a developer laptop cannot help a remote worker. Follow the operating system’s normal installation method, then restart the worker or rebuild the container if its font directories are part of the image. Refresh the system font cache when appropriate for that OS, and verify as the service account.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #2
One CentOS 7 report involving missing or square UTF-8 characters was resolved after the correct fonts were added to the remote server. That is an environment-specific anecdote, not a universal package recipe.
Explicit web or local font loading
Select the family in your HTML and provide a resource the renderer can read:
<meta charset="utf-8">
<style>
@font-face {
font-family: "ReportScript";
src: url("file:///opt/fonts/ReportScript-Regular.ttf") format("truetype");
font-weight: 400;
font-style: normal;
}
body { font-family: "ReportScript", sans-serif; }
</style>
النص أو文字 أو 𐌀 — affected characters
Use a URL or path that the rendering user can read. If the file is local, check whether your wkhtmltopdf invocation permits local-file access and whether pdfkit passes the required option. pdfkit forwards options; it does not load the font itself. Inspect the actual command and options rather than assuming that an @font-face declaration succeeded.
Encoding and CSS checks
Declare UTF-8 in the document and write the input file as UTF-8. Keep the family name identical to the name inside the font. Remove conflicting rules while diagnosing, and specify the weight and style actually present instead of requesting a synthetic variant.
A repeatable diagnostic procedure
- Copy the failing characters into a minimal UTF-8 HTML test.
- Run the exact production
wkhtmltopdfbinary, options, working directory and user account. - Try a font you have verified covers those code points.
- Install or expose that font in the deployment image.
- Set the family explicitly in CSS; use a readable local or served resource.
- Regenerate the PDF and inspect the PDF itself at high zoom. Do not stop at a browser preview.
- Change one variable at a time and retain the generated PDF and command line for comparison.
This isolates font coverage, resource access, fallback and renderer behavior instead of changing several uncertain settings together.
Common symptoms, causes and fixes
| Symptom | Likely cause | Next check |
|---|---|---|
| Blank characters | No usable glyph, failed font resource, or renderer fallback failure | Verify code-point coverage and read permissions; render the minimal test |
| Hollow or black squares | Replacement glyph because no selected/available font covers the character | Try a font known to cover the script and confirm it is visible to the worker |
| Works in Chrome, fails in PDF | Browser and wkhtmltopdf use different fallback fonts or font paths | Inspect fonts in the deployment environment and set an explicit family |
| Font installed but output unchanged | Wrong machine/user, stale worker image, unreadable path, or unsupported shaping | Restart/rebuild, test as the service account, then try another compatible font |
@font-face appears ignored |
Bad URL, local-file restrictions, inaccessible file, or unsupported format | Check renderer logs/options and use a minimal local-resource test |
| Letters present but order or marks are wrong | Script shaping, combining-mark or bidirectional-text limitation | Test a script-capable font and a different wkhtmltopdf build; do not assume cache refresh fixes it |
Why font-cache refreshes and fallback assumptions are insufficient
A cache command can make newly installed fonts discoverable, but it cannot add missing glyphs or repair a renderer limitation. A historical Noto Sans Thaana report remained broken after multiple Noto fonts, @font-face attempts and fc-cache -f -v. Treat cache refresh as one diagnostic step, then prove selection in the generated PDF.
Likewise, a generic fallback stack such as "Preferred", sans-serif is not a guarantee. The first family may lack one character, and the renderer’s fallback set may differ from the browser’s. For critical documents, choose a family with tested coverage and keep the minimal glyph test in continuous deployment.
Deployment, reliability and security considerations
Containers and workers
Put licensed font files in the image or mount them consistently, refresh discovery during image creation, and restart long-lived workers after changes. Confirm the effective user, not only root, can read the files. Record the wkhtmltopdf version because archived issue discussions cannot establish behavior for every current build.
Remote resources
Network-hosted fonts add DNS, TLS, authentication and timing failure modes. A self-contained, readable resource is easier to reproduce. If you must serve a font, verify that the renderer can reach it under production network policy and that your wait settings allow it to load.
Licensing
Check the font license before embedding, redistributing, baking it into a container or exposing it through an application endpoint. Technical coverage is only one part of a production choice.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your goal is reliable website captures rather than debugging a local browser, ScreenshotNeo provides a website screenshot API and MCP server. A GET request returns PNG, JPEG, WebP or PDF, while the service accepts the cookie or consent banner as a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsUse the documented options and parameter names in the ScreenshotNeo documentation. Minimal calls:
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
It also offers an MCP server for Claude, Cursor and other MCP clients, so AI agents can call take_screenshot, get_page_info and capture_pdf. Every plan includes the features; the Free plan provides 1,000 screenshots per month without a card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Can changing only pdfkit options fix a missing glyph?
Usually not. pdfkit can pass renderer options, but the renderer still needs a readable font containing the character.
Should I use the same font as my browser?
Only if that font is installed or explicitly loadable by the wkhtmltopdf process in production and covers the exact code points.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteIs Noto a universal solution?
No. Noto families differ by script, and coverage or shaping support must be verified for the characters you use.
What if installation and CSS selection both fail?
Reduce the case to the minimal test, verify the actual binary and user, then investigate resource restrictions, shaping support, font format and version-specific renderer behavior.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

