ScreenshotNeo

BlogHow-to

PhantomJS Screenshot Fonts Look Wrong: How to Fix Rendering

Diagnose missing or incorrect PhantomJS screenshot fonts by checking the runtime, font requests, host fonts, permissions, and capture timing.

By the ScreenshotNeo team4 October 20267 min read

If PhantomJS screenshots show the wrong typeface, missing glyphs, or a fallback font, there is no documented universal rendering switch that fixes every case. First identify the exact PhantomJS binary and environment, then inspect font requests and page errors, verify the requested font is available to that runtime, and make capture readiness explicit. A page.open() callback is the documented place to call page.render(), but the docs do not promise that web fonts have loaded and been applied by then.

This guide focuses on PNG, JPEG, and other image output. If the issue is selectable text or unexpectedly large output in a PDF, follow the separate PDF notes below; evidence about PDF font rasterization does not establish a general screenshot fix.

1. Confirm the PhantomJS binary and version

Run the checks from the same service, container, CI job, or scheduled task that creates the screenshot. A developer shell may invoke a different installation from the production process.

command -v phantomjs
phantomjs --version

On systems with more than one installation, inspect each executable and how the service starts it. Record the binary path and version with each reproduction. PhantomJS’s troubleshooting guide recommends checking the version and warns that multiple installations can conflict. [PhantomJS troubleshooting]

2. Log resource requests and page errors

Instrument the page before opening it. This example logs request URLs and response status codes, reports page errors, sets a finite resource timeout, and renders after page.open() completes. Replace the target URL and output path. It provides evidence about the load; it does not guarantee that every web font is ready at render time.

var page = require('webpage').create();
var system = require('system');
var url = system.args[1] || 'https://example.com';
var output = system.args[2] || 'shot.png';

page.viewportSize = { width: 1365, height: 900 };
page.settings.resourceTimeout = 20000;

page.onResourceRequested = function (request) {
  console.log('REQUEST ' + request.id + ' ' + request.url);
};

page.onResourceReceived = function (response) {
  if (response.stage === 'end') {
    console.log('RESPONSE ' + response.status + ' ' + response.url);
  }
};

page.onResourceTimeout = function (request) {
  console.log('RESOURCE TIMEOUT ' + request.url);
};

page.onResourceError = function (resourceError) {
  console.log('RESOURCE ERROR ' + resourceError.errorCode + ' ' +
    resourceError.errorString + ' ' + resourceError.url);
};

page.onError = function (message, trace) {
  console.log('PAGE ERROR ' + message);
  trace.forEach(function (frame) {
    console.log('  ' + frame.file + ':' + frame.line);
  });
};

page.open(url, function (status) {
  if (status !== 'success') {
    console.log('PAGE OPEN FAILED: ' + status + ' ' + url);
    phantom.exit(1);
    return;
  }

  console.log('Page opened; rendering ' + output);
  var ok = page.render(output);
  phantom.exit(ok ? 0 : 1);
});

Run it with:

phantomjs capture.js https://example.com shot.png

Look for the font file URL in the resource log. Check whether it was requested, whether it completed, and whether its response indicates failure. Also inspect console output and page errors. PhantomJS’s troubleshooting guide recommends network callbacks and an onError handler. If HTTPS fails while HTTP works, the guide specifically recommends checking SSL libraries in the environment. [PhantomJS troubleshooting]

3. Check font URLs, access rules, and timeouts

Inspect the page’s CSS for the requested family, weight, style, and @font-face URL. A weight or style mismatch can select another face or a fallback. Confirm the URL is reachable from the PhantomJS process, including any authentication, redirects, TLS requirements, or network restrictions.

If a local document loads a remote font, check PhantomJS’s localToRemoteUrlAccessEnabled setting. Check resourceTimeout if font requests are being cut off. These settings apply during the initial page.open(); configure them before calling it. [PhantomJS WebPage API]

page.settings.localToRemoteUrlAccessEnabled = true;
page.settings.resourceTimeout = 20000;
page.open(url, function (status) {
  // Inspect status and resources, then decide when to render.
});

Enable local-to-remote access only when the page needs that access. A longer timeout gives slow resources more time, but it cannot repair a blocked URL, invalid certificate, missing font, or incorrect CSS.

4. Verify the font in the renderer’s environment

Check the operating system user and container that run PhantomJS, not only your workstation. Verify that the exact requested family and needed weights/styles are available or that the remote font can be fetched and decoded. Review the CSS fallback chain and compare the rendered face with a known local page.

The cited PhantomJS documentation does not specify one host-font installation procedure that applies to every OS. Treat host font availability as a diagnostic branch: compare the same script in the actual runtime environment and inspect its font configuration using that platform’s tools.

5. Make capture timing deliberate

The official screen-capture example calls page.render() from the page.open() callback. That callback’s documented behavior is not a guarantee that all web fonts have loaded and been applied. [PhantomJS screen capture]

Use resource logs to establish whether the font request finishes before rendering. If the page has a reliable application-specific ready signal, wait for it with a bounded timeout, then render. A fixed delay can help diagnose a race, but it is not a reliable readiness test: network and page timing vary. Do not assume modern browser APIs such as document.fonts.ready work in PhantomJS’s older WebKit without verifying the specific build.

6. Compare like with like

For a useful reproduction, hold the page URL, PhantomJS binary/build, operating system or container, runtime user, viewport, font files, and output format constant. Change one variable at a time: for example, compare local and remote font loading, or compare the same image capture before and after a font request completes. This controlled comparison is diagnostic guidance; the cited docs do not claim cross-platform font identity.

PhantomJS uses WebKit for page layout and rendering, and its render API documents image formats and says non-PDF image generation uses Qt QImage. The sources do not document a universal switch that makes fonts identical across platforms. [Screen capture] [WebPage API]

7. Diagnose PDF symptoms separately

If the symptom is that PDF text cannot be selected, or the PDF is unexpectedly large, investigate PDF generation independently from PNG/JPEG screenshot rendering. A historical GitHub issue includes a user report of a remotely loaded web font being rasterized and a later Linux-specific report that installing TTF files and running fc-cache -fv helped that user’s PDF case. The same discussion says a dependency upgrade was sufficient for another user. These are anecdotal reports about PDF behavior, not a universal repair for screenshots. [Archived PhantomJS issue discussion]

Common errors and fixes

Symptom Likely cause to check Next step
Font URL never appears in logs CSS did not request that face, a different stylesheet is active, or the page did not reach the relevant state. Inspect computed CSS and the page’s actual stylesheets in the target environment.
Font request errors or times out Blocked URL, TLS/SSL problem, network policy, access setting, or insufficient resource timeout. Read the resource error and URL; verify reachability and configure applicable settings before page.open().
Fallback face despite a successful request Wrong family/weight/style, font decoding problem, or render before the page applies the face. Check CSS declarations and request completion, then compare a capture after the page’s own ready condition.
Works on a workstation but not in CI Different PhantomJS binary, OS libraries, fonts, runtime user, or network access. Record version/path in both environments and compare font availability and resource logs.
Only PDF text selection or size is wrong PDF output path has different behavior from image output. Reproduce as PDF and investigate that output separately; do not apply a reported PDF workaround as a general screenshot fix.

Performance, reliability, and cost considerations

More logging helps isolate a bad request but can produce noisy output on pages with many resources; filter to font URLs once the initial trace identifies them. Increasing timeouts may increase job duration and still cannot ensure a font exists or is usable. A bounded, page-specific readiness condition is usually more predictable than an arbitrary long sleep.

PhantomJS is a legacy project: its GitHub repository was archived on May 30, 2023. Existing systems can use the diagnostic sequence above, while new automation should evaluate migration as a separate decision based on required site compatibility and migration cost. The cited material does not establish a preferred replacement. [PhantomJS GitHub repository]

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One GET request can return a PNG, JPEG, WebP, or PDF. See the API documentation for parameters and options. For a WebP capture:

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}`);

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.

FAQ

Does PhantomJS guarantee web fonts are ready in the page.open() callback?

The screen-capture example renders in that callback, but the cited documentation does not describe it as a web-font readiness guarantee.

Is there one flag that makes fonts render identically on every platform?

No such universal switch is documented in the cited render and screen-capture material.

Does the Linux Fontconfig report fix screenshot images?

It is a user report about selectable text in generated PDFs, so it does not establish a general image screenshot fix.

Should a new project still use PhantomJS?

The repository is archived. Evaluate a maintained alternative against the pages and output formats your automation actually needs.