Back to Blog
Fingerprint

Text Measurement Fingerprinting: Sub-Pixel Font Metrics as a Tracking Signal

Canvas measureText() returns text width values with sub-pixel precision that vary across operating systems due to differences in font rendering engines. Learn how these tiny numerical differences become a reliable platform fingerprint.

Introduction

When a website measures the width of a text string using the Canvas API's measureText() method, the result is a floating-point value with sub-pixel precision. These values vary across operating systems because each platform uses a different built-in rendering engine with its own internal arithmetic model. The differences are tiny, invisible to the human eye, but they are consistent, reproducible, and measurable through JavaScript.

This sub-pixel precision turns measureText() into one of the most reliable platform identification signals available to tracking scripts. Unlike Canvas image fingerprinting, which requires rendering pixels and hashing the result, text measurement fingerprinting is fast, lightweight, and produces a direct numerical value that can be compared instantly. A single call takes microseconds and returns a value that identifies your operating system. When a tracking script measures multiple strings at multiple font sizes, the combined result becomes a strong fingerprint signal.

This makes text measurement a tracking vector actively used by commercial fingerprinting systems across social media, banking, payment processing, and e-commerce websites. It requires no special permissions, generates no user-visible behavior, and produces stable results across browser sessions. As browsers improve protections for other fingerprinting vectors, text measurement is receiving increasing attention because it is difficult to protect at the API level without degrading web application behavior.

BotBrowser's Engine-Level Approach

BotBrowser addresses text measurement fingerprinting at the browser engine level, not through API interception or post-processing. This is a fundamental architectural distinction that enables protection without detection and without breaking web functionality.

Unified Font Rendering Path

BotBrowser modifies the font rendering pipeline within Chromium itself to ensure that font metric calculations use consistent arithmetic across all platforms. Regardless of whether BotBrowser is running on macOS, Windows, or Linux, the font scaling mathematics for a given profile produce identical results.

This means that when a fingerprint profile specifies a particular device configuration, the measureText() results match that configuration exactly, down to the last significant digit. The font engine does not simply return the host system's native values. Instead, it computes metrics using the rendering model defined by the profile.

Profile-Driven Consistency

Each BotBrowser fingerprint profile encodes the text metric characteristics of the profiled device. When the profile is loaded, the font rendering engine produces measurements that match the original device:

  • The same text string at the same font size returns the same width value, regardless of the host operating system.
  • The same profile loaded on macOS, Windows, and Linux produces bit-identical measureText() results.
  • The results are internally consistent with other font-related signals (font list, Canvas text rendering, CSS layout metrics).

This consistency extends to all properties of the TextMetrics object, not just width. The actualBoundingBoxAscent, actualBoundingBoxDescent, fontBoundingBoxAscent, fontBoundingBoxDescent, and bounding box left/right values all match the profile.

Bit-Identical Results Across Platforms

The key technical achievement is bit-identical results. This means the floating-point representation of the measureText() result is identical byte-for-byte across platforms. There is no "close enough" threshold. The values are mathematically identical.

This level of consistency is only possible with engine-level control. Any approach that operates above the rendering engine, such as extensions, API proxies, or JavaScript wrappers, cannot achieve bit-identical results because it cannot control the actual arithmetic performed by the font engine.

BotBrowser achieves this by ensuring that the font scaling math uses the same precision model regardless of the host platform. The profile defines the expected behavior, and the engine produces exactly that behavior. The host system's native font rendering preferences do not affect the output.

No Detectable Artifacts

Because the protection operates within the rendering engine itself:

  • measureText() returns native JavaScript Number values with full double-precision floating-point precision, exactly as an unmodified browser would.
  • There are no prototype modifications, no function wrapping, no JavaScript-layer interception.
  • The values are consistent with Canvas pixel rendering. Text drawn on a Canvas and then measured pixel-by-pixel matches the reported measureText() width.
  • There are no statistical anomalies in the distribution of values across different font sizes and text strings.

Internal Consistency with Other Signals

Text metric protection does not operate in isolation. The profile ensures that measureText() values are consistent with all other observable font and rendering signals. If a tracking script cross-validates text metrics against Canvas pixel output, font enumeration results, or CSS layout measurements, all signals align with the same device identity. This internal coherence is critical, because fingerprinting systems increasingly look for mismatches between different signal types as an indicator of tampering.

Configuration and Usage

CLI Usage

Launch BotBrowser with a fingerprint profile to enable text measurement protection:

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

For deterministic results across runs, add a noise seed:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-noise-seed=12345 \
       --user-data-dir="$(mktemp -d)"

Combine with timezone, locale, and proxy settings for comprehensive identity consistency:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-noise-seed=12345 \
       --proxy-server="socks5://user:pass@proxy:1080" \
       --bot-config-timezone="America/New_York" \
       --bot-config-locale="en-US" \
       --bot-config-languages="en-US,en"

Playwright Integration

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
      '--bot-noise-seed=42',
    ],
    headless: true,
  });

  const context = await browser.newContext({ viewport: null });
  const page = await context.newPage();

  await page.goto('https://example.com');

  // measureText() results will be consistent with the loaded profile
  const metrics = await page.evaluate(() => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '16px Arial';
    const measurement = ctx.measureText('Sample text for measurement');
    return {
      width: measurement.width,
      actualBoundingBoxAscent: measurement.actualBoundingBoxAscent,
      actualBoundingBoxDescent: measurement.actualBoundingBoxDescent,
    };
  });

  console.log('Text metrics:', metrics);

  await browser.close();
})();

Puppeteer Integration

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
      '--bot-noise-seed=42',
    ],
    headless: true,
    defaultViewport: null,
  });

  const page = await browser.newPage();
  await page.goto('https://example.com');

  // Verify text measurement from the loaded profile
  const metrics = await page.evaluate(() => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '16px Arial';
    const m = ctx.measureText('Sample text for measurement');
    return { width: m.width };
  });

  console.log('Text width:', metrics.width);

  await browser.close();
})();

Verification

After launching BotBrowser with a profile, verify that text measurement protection is active:

const width = await page.evaluate(() => {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  ctx.font = '16px Arial';
  return ctx.measureText('Verification string').width;
});
console.log('Text width:', width);

What to check:

  1. The width value is a full-precision floating-point number, consistent with what a real device with the profile's target platform would return.
  2. Reloading the page and repeating the measurement produces the same value.
  3. Restarting the browser with the same profile and noise seed produces the same value.
  4. Visit fingerprint testing sites like BrowserLeaks or CreepJS and confirm that no cross-signal inconsistencies are flagged.

Best Practices

Always Use a Complete Profile

Text measurement protection works as part of BotBrowser's complete fingerprint profile. The profile ensures that text metrics are consistent with all other browser signals: Canvas rendering, font lists, WebGL parameters, and system information. Loading a profile is required for text measurement protection to be active.

Combine with Network Identity

Text metrics identify the platform. Network signals identify the location. For consistent identity, combine the fingerprint profile with appropriate proxy and locale settings:

chrome --bot-profile="/path/to/profile.enc" \
       --proxy-server="socks5://user:pass@proxy:1080" \
       --bot-config-timezone="Europe/London" \
       --bot-config-locale="en-GB" \
       --bot-config-languages="en-GB,en"

Use Deterministic Mode for Testing

When running automated tests or research experiments, use --bot-noise-seed to ensure reproducible results. This allows you to verify that text metrics remain stable across test runs and to detect any regressions in metric consistency.

Test with Multiple Font Configurations

When verifying protection, test with a variety of font families and sizes. Include both common web fonts and system defaults in your test set. This ensures comprehensive coverage of your profile's text metric consistency across the range of fonts that tracking scripts typically probe.

Keep Profiles and BotBrowser Updated

Text measurement fingerprinting continues to evolve. New TextMetrics properties have been added in recent browser versions, and tracking scripts update their collection to include these new signals. Keep your BotBrowser installation and profiles updated to ensure coverage of newly exploitable properties.

FAQ

What is text measurement fingerprinting?

Text measurement fingerprinting uses the Canvas measureText() API to determine which platform a browser is running on. The API returns text width values with sub-pixel precision, and these values differ between operating systems because each OS uses a different built-in rendering engine. By measuring specific text strings at specific sizes, a tracking script can identify the operating system and distinguish individual devices.

How is this different from Canvas fingerprinting?

Canvas fingerprinting renders visual content (text, shapes, gradients) to a Canvas element and hashes the resulting pixel data. Text measurement fingerprinting uses measureText() to get numerical width values without rendering anything visible. Text measurement is faster, produces a direct numerical result, and provides a different type of signal. Both techniques rely on platform-specific rendering differences, but they operate on different outputs of the Canvas API. You can learn more about Canvas fingerprinting in our Canvas Fingerprinting guide.

Does BotBrowser produce identical values on all platforms?

Yes. When the same fingerprint profile is loaded, BotBrowser produces bit-identical measureText() results on macOS, Windows, and Linux. This is achieved through engine-level control of the font rendering math, ensuring that the same profile always produces the same numerical results regardless of the host operating system.

Does text measurement protection break websites?

No. BotBrowser does not block or modify the measureText() API. The API works normally, returning accurate text width values that web applications can use for layout, text truncation, chart rendering, and all other legitimate purposes. The values are simply consistent with the loaded profile rather than with the host system's native rendering.

How does this relate to font fingerprinting?

Text measurement fingerprinting is closely related to font fingerprinting but targets a different signal. Font fingerprinting identifies which fonts are installed by checking whether specific font families are available. Text measurement fingerprinting identifies how the rendering engine processes those fonts by examining sub-pixel metric values. Both signals are derived from the font rendering stack, and both are covered by BotBrowser's engine-level protection. For a broader overview, see our article on browser fingerprinting.

Can I use BotBrowser's text measurement protection with existing automation frameworks?

Yes. BotBrowser integrates with Playwright, Puppeteer, Selenium, and other automation frameworks. Text measurement protection is automatically active when a fingerprint profile is loaded. No additional configuration or code changes are needed beyond passing the --bot-profile flag. See the Playwright getting started guide and Puppeteer getting started guide for setup instructions.

Does this protection cover all TextMetrics properties?

Yes. BotBrowser's protection covers the full set of TextMetrics properties, including width, actualBoundingBoxLeft, actualBoundingBoxRight, actualBoundingBoxAscent, actualBoundingBoxDescent, fontBoundingBoxAscent, and fontBoundingBoxDescent. All values are consistent with the loaded profile across all platforms.

Summary

Text measurement fingerprinting uses the sub-pixel precision of the Canvas measureText() API to identify browsers by their rendering engine. Because each operating system uses a different built-in rendering engine with its own internal arithmetic model, the same text at the same font size produces measurably different width values on each platform. These differences are consistent, reproducible, and actively collected by commercial tracking systems.

BotBrowser addresses text measurement fingerprinting at the browser engine level by controlling the font rendering math within Chromium itself. When a fingerprint profile is loaded, the font scaling calculations produce bit-identical results across macOS, Windows, and Linux. There are no API-level hooks, no prototype modifications, and no statistical anomalies. The values are internally consistent with all other font and Canvas signals in the profile, ensuring that cross-validation checks by fingerprinting scripts find no mismatches.

Combined with BotBrowser's protection for Canvas rendering, font enumeration, and cross-platform profile consistency, text measurement protection closes one of the most precise fingerprinting vectors available to tracking systems today.

#text measurement#measureText#fingerprinting#font metrics#canvas#sub-pixel#privacy#cross-platform

Ready to protect your browser fingerprint?

BotBrowser provides engine-level fingerprint control with real device profiles. Start with a free tier or explore all features.