Boxes (tofu glyphs) in a PhantomJS screenshot almost always mean that the selected font and its fallbacks do not contain the requested character. Install a font with the right Unicode coverage where Linux Fontconfig can see it, rebuild the Fontconfig cache with fc-cache -vf, restart PhantomJS, and render only after web fonts have finished loading. English working while Japanese, Chinese, symbols, Arabic or emoji appear as boxes is a coverage problem, not a screenshot-size problem.
What a box glyph tells you
A missing-glyph box is drawn when the shaping engine cannot find a usable glyph in the chosen family or any fallback family. CSS can name a font that is not installed, or a locally installed Latin font can lack CJK, Arabic, symbol or emoji ranges. PhantomJS uses QtWebKit; on Linux, QtWebKit obtains fonts through Fontconfig. The renderer therefore needs both a font file with the required characters and a Fontconfig configuration/cache visible to the account that runs the job.
| # | Preview | Product | Price | |
|---|---|---|---|---|
| 1 |
|
The Phantom of the Opera (Full Screen Edition) | $15.16 | Buy on Amazon |
| 2 |
|
The Phantom of the Opera at the Royal Albert Hall | $8.99 | Buy on Amazon |
| 3 |
|
The Phantom of the Opera (Two-Disc Special Edition) | $16.49 | Buy on Amazon |
| 4 |
|
Phantom of the Opera | $9.49 | Buy on Amazon |
| 5 |
|
The Phantom of the Opera (2004) | Buy on Amazon |
PhantomJS can still produce a valid PNG or PDF when glyphs are missing. A successful page.render() call does not prove that text was shaped correctly.
Diagnose the missing range before changing the image code
Compare scripts and symbols
Create a small test string containing representative characters from every script your page uses: Latin, punctuation, currency and mathematical symbols, Arabic, Japanese kana and kanji, Chinese characters, and the emoji you actually need. If only one range fails, install coverage for that range instead of replacing every font in the image.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
- This Certified Refurbished product is tested and certified to look and work like new. The refurbishing process includes functionality testing, basic cleaning, inspection, and repackaging. The product ships with all relevant accessories, a minimum 90-day warranty, and may arrive in a generic box.
Inspect the computed stack
Use browser developer tools on the source page (or temporarily print the CSS) to record the computed font-family, weight and style for the element. A family name in CSS is only a request; it is not evidence that the same face exists in the PhantomJS machine. Check the runtime account, not just your desktop account.
Check Fontconfig as the PhantomJS user
Fontconfig can use configured directories and caches, and its location can be overridden with FONTCONFIG_FILE and FONTCONFIG_PATH. Run inventory commands as the same user and inside the same container or service unit that launches PhantomJS:
fc-match 'sans-serif'
fc-list | head
printf 'FONTCONFIG_FILE=%snFONTCONFIG_PATH=%sn' "$FONTCONFIG_FILE" "$FONTCONFIG_PATH"
If the expected family does not appear, PhantomJS cannot use it yet. If it appears for your shell but not for the job, compare the service environment, user, mount points and container image.
Install a font with the coverage your page needs
Distribution packages
Use your Linux distribution’s package manager when a maintained package supplies the required script. Package names differ between Ubuntu, CentOS and their releases, so verify the package’s actual coverage and license rather than copying a name from another distribution. The PhantomJS community has reported Japanese boxes being corrected with IPA Gothic or IPA Mincho; treat that as an environment-specific example, not a universal package prescription.
Rank #2
Licensed files in a controlled directory
For a reproducible image, obtain a legally licensed TTF or OTF containing the needed Unicode ranges and place it in a directory present in the same machine or container layer as PhantomJS. This example uses /opt/phantom-fonts:
sudo install -d -m 0755 /opt/phantom-fonts
# Copy the licensed TTF/OTF files into /opt/phantom-fonts
sudo fc-cache -vf /opt/phantom-fonts
fc-match 'Your Font Family'
Replace Your Font Family in the check with the family name embedded in the file. A file name and a CSS family name are not necessarily identical. A user-level directory is also valid for an unprivileged job, provided the job’s user can read it and the cache is rebuilt for that user.
Coverage is not interchangeable
| Content that is missing | What to install or verify | Common mistake |
|---|---|---|
| Latin and punctuation | A complete Latin face with the weights and styles requested by CSS | Installing only a bold or italic file while normal text requests another face |
| Symbols and uncommon punctuation | A symbol-capable font, then a fallback in the CSS stack | Assuming a UI font covers every mathematical or currency character |
| Arabic or another complex script | A font covering that script and its shaping forms | Testing isolated letters but not joined text or right-to-left runs |
| Japanese, Chinese or Korean | A CJK font covering the exact characters in your content | Using a Latin-only web font and expecting CJK fallback to be automatic |
| Emoji | An emoji-capable font and a fallback strategy appropriate to the renderer | Treating emoji as ordinary monochrome letters |
Keep licensing records with the image definition. A font that renders correctly but is not licensed for server-side distribution is not a production fix.
Refresh Fontconfig and restart PhantomJS
- Install or copy the font into a directory Fontconfig reads.
- Run
fc-cache -vf(or target the directory explicitly) as the account that owns the runtime cache. - Verify the family with
fc-matchandfc-listunder that same account. - Restart the PhantomJS process. A long-lived process can retain the old font inventory even after the on-disk cache changes.
- Capture a test page containing representative characters from every required script.
Refreshing a host cache does not update a separate container layer, read-only image or different user cache. Bake the font installation and cache refresh into the image or startup procedure used by CI.
Recommended Free Tools
Rank #3
- DVD
- AC-3, Closed-captioned, Color
- English (Subtitled), Spanish (Subtitled), French (Subtitled)
- 2
- 141
Use a bundled web font when the page owns the asset
A private page can ship a licensed font beside its HTML and reference it with @font-face. This avoids depending on host fonts, but PhantomJS must be able to reach the URL and finish loading it before rendering.
<style>
@font-face {
font-family: 'Report CJK';
src: url('fonts/report-cjk.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
.report { font-family: 'Report CJK', sans-serif; }
</style>
Serve the font over a URL that PhantomJS can access. Check URL-access restrictions, HTTPS certificate behavior, authentication and any cross-origin policy. A stylesheet that loads while the font request fails still produces boxes.
Render from the page-load callback or from a controlled wait that confirms the font request completed. PhantomJS does not provide the modern document.fonts.ready workflow, so instrument requests and use a bounded delay when necessary:
var page = require('webpage').create();
page.settings.resourceTimeout = 30000;
page.onResourceError = function (e) {
console.log('resource error: ' + e.url + ' — ' + e.errorString);
};
page.onConsoleMessage = function (m) { console.log('console: ' + m); };
page.open('https://example.com/report', function (status) {
if (status !== 'success') {
console.log('page.open failed: ' + status);
phantom.exit(1);
return;
}
// Allow the @font-face request and layout to settle; keep this bounded in CI.
window.setTimeout(function () {
page.render('/tmp/report.png');
phantom.exit();
}, 1500);
});
Use request logging to identify a 404, timeout or blocked font URL. If the page depends on a font loaded after JavaScript changes the DOM, wait for that application state rather than relying on an arbitrary long sleep.
Rank #4
- Format: Closed-captioned, Color, Dolby, NTSC, Subtitled, Widescreen
- Language: English (Dolby Digital 5.1), French (Dolby Digital 5.1)
- Subtitles: English, French, Spanish
- Region 1 (U.S. and Canada only); Number of discs: 1
- Rated: PG-13; Run Time: 141 minutes
A repeatable repair workflow for CI
- Record the failure. Save the URL, PhantomJS version, operating-system image, runtime user, CSS stack and a screenshot containing the failing characters.
- Classify the range. Decide whether the failure is Latin, symbols, emoji, Arabic or CJK. Test a minimal page so unrelated CSS cannot hide the cause.
- Choose one source of truth. Use a distribution package for a base image shared by many pages, or a versioned licensed font asset for one application.
- Install where the job runs. Put the files in a Fontconfig directory in the actual container or machine image. Do not rely on a developer workstation.
- Rebuild and verify. Run
fc-cache -vf, thenfc-matchandfc-listas the PhantomJS user. - Restart the renderer. Start a fresh PhantomJS process after the cache update.
- Handle web fonts separately. Confirm the font request succeeds and delay
page.render()until the page has applied it. - Lock the baseline. Keep the OS image, font files, cache step and representative-character test in source control or image build scripts.
Troubleshooting symptoms, causes and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| English renders, Japanese or Chinese is boxed | No CJK coverage in the selected and fallback fonts | Install a licensed CJK face, rebuild Fontconfig, restart PhantomJS, and test the exact characters. |
fc-list shows the font, but the screenshot does not |
PhantomJS runs as another user, in another container layer, or with different FONTCONFIG_FILE/FONTCONFIG_PATH |
Run all checks inside the renderer’s environment and align its mounts and variables. |
| The first capture is boxed; later captures work | The web font was still downloading when page.render() ran |
Log the request, wait for completion or a bounded readiness condition, then render. |
| Only one weight or italic style is boxed | The requested face is not installed or the @font-face declaration maps the wrong weight/style |
Install the matching face or correct the declaration and verify computed CSS. |
| Font works locally but fails in CI | Fonts and caches are not part of the CI image | Bake installation and fc-cache -vf into the same image used by the job. |
| Web font request reports an error | Bad URL, certificate, access restriction, authentication or cross-origin policy | Use a reachable URL, inspect PhantomJS resource logs and make the asset available before capture. |
| Boxes remain after installing a font | The new font still lacks the code points, or a CSS rule selects another family | Check coverage and computed family; add an appropriate fallback instead of assuming the install failed. |
Choose an approach for repeatability
| Approach | Best use | Main risk | Reproducibility |
|---|---|---|---|
| System font package | Stable CI images and multiple pages | Package differs by distribution and may be incomplete | High when baked into the image |
Bundled @font-face |
One page or application with a controlled font | URL or CORS/loading failures and license obligations | High when assets are versioned |
| User-level font directory | Unprivileged jobs or containers | Wrong runtime user or stale cache | Medium unless scripted |
| Browser migration | Long-term maintenance | Screenshot-baseline changes | High after the new image is pinned |
Plan for PhantomJS’s suspended development
The official PhantomJS homepage states, “Important: PhantomJS development is suspended until further notice.” Existing jobs can be stabilized with a pinned image, explicit fonts and regression tests, but new browser behavior and font technologies will not be added to PhantomJS. Treat a migration to a maintained browser as a separate project: pin the replacement browser and OS image, install the same licensed fonts, compare representative scripts, and approve intentional anti-aliasing or line-break changes instead of masking them as font fixes.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. One request returns a PNG, JPEG, WebP or PDF without you maintaining a PhantomJS installation. Before capture it accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status.
For a direct call, see the ScreenshotNeo API documentation:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Every plan includes full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper size/margins/orientation/page ranges, HTML/CSS-to-image, custom CSS and JavaScript, pre-capture clicks, hidden selectors, waits for a selector, delay or network idle, blocking ads/trackers/requests/resource types, custom headers/cookies/user agent/Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed links for public <img> tags, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification and compatibility with parameter names used by other screenshot APIs. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems| Plan | Included shots | Price |
|---|---|---|
| Free | 1,000 per month | $0, no card |
| Starter | 3,000 | $5 |
| Growth | 15,000 | $15 |
| Pro | 60,000 | $39 |
| Scale | 250,000 | $99 |
| Business | 1,000,000 | $249 |
Yearly billing gives two months free, and every feature is on every plan. If you want to stop maintaining browser binaries, font packages and cache steps, start with 1,000 free screenshots a month with no card; paid plans start at $5 for 3,000 shots.
Best Value
Frequently Asked Questions
Can a viewport or device preset cause missing-glyph boxes?
Changing viewport, device scale or page dimensions can alter wrapping, but it does not add glyphs to a font. Treat boxes as a font-coverage or loading problem first.
Is a successful HTTP response proof that a web font was used?
No. The page can return successfully while the font request is blocked, late or mapped to a different family. Inspect the resource log and computed family, then compare a capture made after the font is ready.
Should font files be copied into every PhantomJS worker?
Yes, each worker needs the same licensed files, Fontconfig visibility and refreshed cache in its own machine or container environment. A cache on one host is not shared automatically with another worker.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →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.

