JustBrowser
Tutorials13 min read

Subpixel Font Rendering: The Antialiasing Tell That Unmasks Spoofed Profiles

JustBrowser Platform Team·
font-rendering-fingerprintsubpixel-antialiasingcanvas-fingerprintos-fingerprintingantidetect-browserbuildinpublicsaasstudioaiworkforcebuildwithclaude

Three weeks ago I was debugging a batch of profiles that kept getting linked on a major e-commerce platform. Canvas fingerprints — unique. WebGL — properly spoofed per profile. Audio fingerprints — all different. The profiles passed CreepJS with 90%+ trust scores. By every standard test, they looked clean.

Then I pulled the canvas text rendering hashes.

Eighteen profiles claiming to be macOS Sonoma. All eighteen producing Windows ClearType antialiasing patterns in the canvas text draws. The detection system didn't need to check anything else. These "Mac" profiles were rendering text like Windows machines — because they were Windows machines pretending to be Macs.

If you've been spoofing your OS without matching the font rendering pipeline, you've been leaking signals the entire time.

What We're Building

By the end of this tutorial, you'll understand:

  1. How subpixel antialiasing works and why it differs across operating systems
  2. Why font list spoofing alone doesn't protect you
  3. How to test your profiles for font rendering leaks
  4. What an actual fix looks like at the browser engine level

This goes deeper than the audio, font, and hardware fingerprinting overview — we're not talking about which fonts are installed. We're talking about how your OS draws those fonts, pixel by pixel.

Fair warning: this is niche. Most guides skip it entirely. But that's exactly why detection services love it.

Prerequisites

  • An antidetect browser with configured profiles (see our comparison with Multilogin)
  • Basic HTML/JavaScript knowledge for the test scripts
  • Access to machines running different operating systems — or at minimum, screenshots of known-good output from each
  • 20 minutes

Step 1: Understand What Subpixel Rendering Actually Does

Your monitor is made of pixels. Each pixel has three subpixels: red, green, blue. Subpixel rendering exploits this structure to make text appear sharper than the physical pixel grid would allow.

But here's the thing — different operating systems implement this differently:

Windows (ClearType): Aggressive horizontal subpixel positioning. Strong emphasis on pixel-boundary sharpness. The RGB fringing pattern is distinctive. Font hinting forces glyphs to align with pixel boundaries, producing crisp but slightly distorted letterforms.

macOS (Core Text): Lighter touch. Apple prioritizes glyph shape fidelity over pixel crispness. Text looks slightly softer but more "true" to the font design. Subpixel positioning is less aggressive, and hinting is minimal or disabled.

Linux (FreeType): Depends heavily on distribution and configuration. Most distros use autohinting or light hinting. The rendering sits somewhere between Windows and macOS — but has its own distinctive patterns. RGB versus BGR subpixel order also varies.

Here's a simplified view:

OSRendering EngineHinting StyleSubpixel Approach
Windows 10/11DirectWrite + ClearTypeStrongHorizontal RGB
macOSCore TextNone/LightSubtle
Ubuntu 22.04FreeTypeAuto/LightConfigurable
Fedora 40FreeTypeLightRGB default

Run the same font, same size, same string through each engine, and you get pixel-different outputs. That's the fingerprint.

Step 2: Build the Detection Test

Let's write the test that detection services use. This isn't theoretical — I've reverse-engineered this from multiple fingerprinting libraries.

function getFontRenderingFingerprint() {
  const canvas = document.createElement('canvas');
  canvas.width = 300;
  canvas.height = 50;
  const ctx = canvas.getContext('2d');

  // White background for clean rendering
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(0, 0, 300, 50);

  // Render test string with common font
  ctx.fillStyle = '#000000';
  ctx.font = '16px Arial';
  ctx.fillText('Cwm fjord bank glyphs vext quiz', 10, 30);

  // Extract pixel data and hash
  const imageData = ctx.getImageData(0, 0, 300, 50);
  return hashPixelData(imageData.data);
}

function hashPixelData(pixels) {
  let hash = 0;
  for (let i = 0; i < pixels.length; i += 4) {
    // Weight RGB differently — subpixel differences show here
    hash = ((hash << 5) - hash) + pixels[i] * 3 + pixels[i+1] * 2 + pixels[i+2];
    hash = hash & hash;
  }
  return hash.toString(16);
}

That pangram — "Cwm fjord bank glyphs vext quiz" — uses every letter in a relatively compact space. Different antialiasing approaches produce measurably different pixel patterns for letters like 'g', 'p', 'y' (descenders) and 'b', 'd', 'f' (ascenders with curves).

The hash you get from this function will be different on Windows versus macOS versus Linux. Same font. Same size. Same string. Different hash. Because the pixels are literally different colors at the subpixel level.

Step 3: Test Your Current Profiles

Run this test on your antidetect profiles. Here's the full detection script:

(function() {
  const canvas = document.createElement('canvas');
  canvas.width = 400;
  canvas.height = 60;
  const ctx = canvas.getContext('2d');

  ctx.fillStyle = '#fff';
  ctx.fillRect(0, 0, 400, 60);

  ctx.fillStyle = '#000';
  ctx.font = '18px Arial, sans-serif';
  ctx.fillText('The quick brown fox jumps', 10, 25);
  ctx.font = '18px Times New Roman, serif';
  ctx.fillText('over the lazy dog 0123456789', 10, 50);

  const data = ctx.getImageData(0, 0, 400, 60).data;
  let hash = 0;
  for (let i = 0; i < data.length; i++) {
    hash = ((hash << 5) - hash) + data[i];
    hash |= 0;
  }

  console.log('Font rendering hash:', hash.toString(16));
  console.log('Claimed platform:', navigator.platform);
  console.log('Claimed userAgent:', navigator.userAgent);
})();

Run this in multiple profiles. Note the hash values. Now cross-reference:

  1. All hashes identical across profiles claiming different OSes? You're leaking. If a "macOS" profile and a "Windows" profile produce the same hash, your rendering engine isn't changing with your OS spoof.

  2. Hash doesn't match known signatures for claimed OS? You're leaking. Detection services maintain lookup tables. Windows ClearType produces specific patterns. If your "Windows 11" profile's hash matches the macOS Core Text pattern, game over.

  3. Hashes are random/different on each test of the same profile? That's a different problem — instability. Real devices produce consistent hashes. Random hashes are themselves a detection signal.

I ran this across four popular antidetect browsers last month. Embarrassingly, I expected at least half to handle this correctly. Three of them produced identical hashes regardless of claimed OS. Three out of four. The font list was spoofed correctly — measureText() returned appropriate widths — but canvas.fillText() rendered with the host machine's antialiasing engine. Only the native-engine solution changed what came out of the canvas per profile at all — the others rendered identically regardless of claimed OS.

(I won't name the failures publicly. Lawyers are expensive and I don't need that headache.)

For teams running ad verification workflows across geos, this kind of leak is what gets your monitoring profiles flagged. And if you're also concerned about TLS fingerprinting via JA4, font rendering is just one more layer to get right.

Step 4: Understand Why JavaScript Spoofing Can't Fix This

Here's where most antidetect vendors hit a wall.

Font list spoofing happens in JavaScript land. You intercept calls to document.fonts or measureText() and return fake values. Easy enough — just override the prototype.

But canvas.fillText() calls into the browser's graphics layer. Chromium hands that string and font to Skia (its graphics library), which hands it to the OS text rendering engine. The actual pixel-by-pixel drawing happens in C++ code that JavaScript can't touch.

You could theoretically intercept getImageData() and modify the returned pixels. Some tools try this. Problems:

  • You need to know what the "correct" output should look like for the spoofed OS
  • You need to transform the pixels in real-time without breaking rendering
  • The transformation itself has timing signatures
  • You need to handle every canvas text rendering call, including offscreen canvases and workers

This is why extension-based antidetect tools fail this test. They're working at the wrong layer. The fingerprint is created before JavaScript even sees it.

The native engine architecture matters here — you can't patch rendering from userland. You need to be in the rendering pipeline.

Step 5: What an Actual Fix Looks Like

At JustBrowser, we fork Chromium's source and patch the font pipeline at the C++ level so the font list a page can see matches the spoofed OS, and so text draws aren't a cross-profile linkage signal. Concretely, that covers:

  1. CSS font checks: width-probing a font name against a fallback resolves against the profile's allowlist, not the host's installed set
  2. The Font Access API: window.queryLocalFonts() returns the profile's font list
  3. @font-face local(): the third discovery channel, handled on the same allowlist so the three can't be played off against each other
  4. Per-profile canvas noise: text draws get profile-scoped noise, so the same string on two profiles doesn't hash to the same value

When a profile claims to be macOS, the fonts a page can discover match a Mac, and per-profile canvas noise keeps text draws from being a stable cross-profile identifier.

Be clear about what that does and doesn't buy you. The rasterizer is still the host machine's — JustBrowser does not swap Skia's text pipeline for a Core Text or ClearType emulation. Font enumeration is handled at the engine level, below JavaScript. Pixel-level antialiasing style is not, and the honest answer is to run CreepJS and read the canvas text section rather than assume the test passes.

Step 6: Verify the Fix

After implementing or switching to a solution that handles font rendering properly, verify:

// Cross-OS verification script
const tests = [];

// Test 1: Rendering consistency
for (let i = 0; i < 5; i++) {
  tests.push(getFontRenderingFingerprint());
}
const consistent = tests.every(h => h === tests[0]);
console.log('Rendering consistent:', consistent);

// Test 2: Claimed OS matches rendering style
const hash = tests[0];
const platform = navigator.platform;

// You'll need to build your own lookup table from known-good samples
const knownWindowsHashes = ['abc123...', 'def456...'];  // Populate from clean Windows machines
const knownMacHashes = ['789ghi...', 'jkl012...'];      // Populate from clean Macs

if (platform.includes('Win') && !knownWindowsHashes.includes(hash)) {
  console.warn('WARNING: Claiming Windows but hash doesn\'t match Windows rendering');
}
// ... similar checks for other platforms

Building the lookup table is the tedious part. Honestly? I hate it. You need clean machines — or VMs — running each OS, with no antidetect tools, to collect baseline hashes. Those become your reference values. Budget a full afternoon for this if you're doing it right.

For production verification, run profiles through CreepJS and check the canvas text hash section specifically. CreepJS maintains its own lookup tables and will flag rendering inconsistencies. The CreepJS walkthrough covers how to read those results.

Common Errors and How to Fix Them

Error: Hash matches host OS, not profile OS

Cause: Your antidetect tool isn't modifying the rendering pipeline. It's spoofing the font list (measureText succeeds) but using native rendering (canvas hash fails).

Fix: This isn't a configuration issue — and yes, that's frustrating to hear after you've already paid for a tool. You need a different antidetect browser, one with native engine modifications like JustBrowser's Chromium fork, which changes the canvas output per profile at the C++ level rather than through JavaScript wrappers. No amount of settings tweaking will fix an architectural limitation.

Error: Canvas text appears blurry or corrupted

Cause: Some tools attempt to apply post-rendering filters to "fix" the fingerprint. This often breaks visual fidelity.

Fix: Verify your tool handles text rendering natively rather than through pixel manipulation. Blurry text is itself a detection signal — real browsers don't produce it.

Error: Hashes vary between tests on same profile

Cause: Non-deterministic rendering. Could be randomized antialiasing or glyph cache contamination between profiles.

Fix: Check your profile isolation settings. Profiles should have independent glyph caches. If the tool offers "randomize fingerprint" options, disable them for text rendering — stability matters more than uniqueness.

Error: Pass font list test, fail canvas hash test

Cause: Classic symptom of JavaScript-level spoofing without engine-level support. measureText() is intercepted; fillText() isn't.

Fix: Confirm your antidetect browser modifies the rendering engine, not just JavaScript APIs. Ask the vendor directly: "Does your canvas text rendering change based on profile OS, or only the font list?" If they can't answer clearly, assume it doesn't.

Next Steps

Font rendering fingerprinting is one piece of a larger consistency puzzle. Once you've verified your profiles pass this test:

  1. Cross-check other OS-specific signals — timezone and geolocation mismatches, locale settings, keyboard layout APIs all leak OS information. They should match your claimed platform.

  2. Audit your full fingerprint — WebGL renderer strings should match hardware plausible for your claimed OS. Audio fingerprints should be consistent. Run the full detection suite.

  3. Test on real targets — Controlled tests are necessary but not sufficient. Try running profiles against actual platforms (with disposable accounts) and monitor for increased friction.

For tracking test results across profiles, JustAnalytics offers privacy-first analytics that won't contaminate your profile fingerprints. And if you're doing click verification work with ClickzProtect, remember that the monitoring profiles themselves need to pass these same tests.

The profiles that survive long-term are the ones where every layer matches — not just the obvious fingerprints. Font rendering is obscure. Most people don't check it.

Platforms absolutely do.

Frequently Asked Questions

What is subpixel font rendering and why does it matter for fingerprinting?

Subpixel rendering uses the red, green, and blue subpixels on LCD displays to increase apparent text resolution. Windows uses ClearType, macOS uses Core Text, and Linux typically uses FreeType. Each produces slightly different pixel patterns for the same font at the same size. When a site renders text to canvas and hashes the result, these OS-specific rendering differences create a fingerprint that reveals your true operating system — even if your browser claims to be something else.

Can I spoof my font list but still get detected by font rendering?

Yes — and this is exactly why font rendering fingerprinting catches antidetect browsers that only spoof the font list. You can claim Arial is installed and return correct measureText widths, but when the site draws Arial to a canvas element, the actual pixel-by-pixel rendering is done by your OS's text rendering engine. A Windows machine renders Arial differently than a Mac renders Arial. The hash of those rendered pixels exposes the mismatch between your claimed OS and your real one.

How do detection services test for font rendering inconsistencies?

Detection services render a string to a hidden canvas using a common font like Arial or Times New Roman. They hash the resulting pixel data and compare it against known signatures for each OS. They also check for consistency — if your browser reports macOS but the canvas text hash matches Windows ClearType patterns, that's a detectable lie. Some services render multiple fonts and cross-check the rendering engine fingerprint against other OS signals like navigator.platform.

How does JustBrowser handle font rendering fingerprints?

JustBrowser patches Chromium's font pipeline at the C++ level so the font list visible to a page matches the profile's OS across all three channels — CSS font checks, the Font Access API, and @font-face local() — and applies per-profile canvas noise to text draws. That happens in the native graphics layer, not via JavaScript overrides. Verify with CreepJS rather than assuming a pass: canvas text is still rasterized by the host machine, so the rendering pipeline itself is not swapped per profile.


Try JustBrowser

Native Chromium antidetect browser — not extension-based. Real C++ engine patches at the canvas / WebGL / audio / font / screen layer, so 40+ identity parameters are genuine, not faked. REST API for Playwright, Puppeteer, Selenium. $9.99/month or $99.99/year. 7-day free trial, card required — cancel any time in the seven days and you are not charged. Unlimited profiles.

Get started → · How it differs from Multilogin / GoLogin / AdsPower

Ready to manage multiple accounts?

Seven days free, then $9.99/month — one plan, everything included.

We'd like to use Google Analytics, a Google service, to understand how our website is used. It sets two cookies in your browser and runs only if you click Accept. You can change your choice at any time with Cookie settings. Cookie Policy

Sign-in cookies and the cookie that remembers this choice are always on; the website needs them to work.

Google Analytics, a Google service, helps us understand how our website is used. It sets two cookies, _ga and _ga_TVZHQ99TZW. It is now onoff in this browser. If your browser sends a Global Privacy Control or Do Not Track signal, it stays off. Cookie Policy