Font Fingerprinting: How measureText() Detects Your Installed Fonts
For privacy-sensitive pages, font fingerprinting detects which fonts are installed on your system. A script renders a test string in a probe font using CanvasRenderingContext2D.measureText() and compares the measured pixel width to the fallback font width. When the measured width matches the fallback, the probe font is absent; when the width differs, the probe font is installed. This detection method runs silently in any browser context and requires no canvas image export. The W3C fingerprinting guidance specifically cites font enumeration as a high-risk fingerprinting vector because the installed font set reflects the OS, locale, installed software portfolio, and even professional specialization of the user.1 Consequently, an Illustrator user has Adobe Fonts; a developer has JetBrains Mono and Fira Code; a gamer may have specialized peripheral vendor fonts.
Building on this, a script probing 200 fonts can enumerate the installed set completely in under 100 milliseconds, making it one of the fastest high-entropy fingerprinting techniques available.2
How canvas-based font probing works
Font probe detection uses CanvasRenderingContext2D.measureText() rather than canvas pixel export. The script creates a canvas, obtains a 2D context, sets a specific font size, and renders a test string like "mmmmmmmmmmlli" in each probe font.3 It calls measureText() on the test string and records the returned pixel width. The same test string in the fallback font (typically "monospace" or "sans-serif") has a known baseline width. When the probe font is installed, the measured width differs from the baseline because the font uses different glyph widths.
Why font probing is hard to notice
When the probe font is absent, the browser falls back to the default font and the measured width matches the baseline. Consequently, no canvas pixel buffer is exported; measureText() returns a number, not an image, which means canvas image blocking does not prevent font detection. Building on this, a script probing 100 fonts requires only 100 measureText() calls, completing in under 30 milliseconds on most hardware without triggering any visible browser behavior.
The speed is what makes font probing dangerous rather than merely possible. Each individual measurement finishes in a fraction of a millisecond, so a full scan of hundreds of fonts adds no perceptible delay to page load. You can watch the effect yourself in the font section of the fingerprint inspector, where the complete enumerated set appears instantly even though the script tested far more typefaces than a human could read.4
Why font sets identify OS, locale, and installed software
The installed font set is a deterministic function of three factors: the OS default installation, the locale settings, and the installed application portfolio. Windows 11 installs a specific set of Microsoft fonts absent from macOS. macOS installs a different set of Apple-licensed fonts, including several typefaces not present on Windows.5 Linux distributions vary widely, with some shipping only open-source fonts and others including commercial fonts under OEM licenses.
Beyond the OS defaults, locale settings add language-specific fonts: East Asian language packs include CJK typefaces not present in Western locale installations. Furthermore, application installations predictably add specific fonts: Adobe Creative Cloud adds Adobe Fonts, Microsoft 365 adds additional Office fonts, JetBrains IDE installs JetBrains Mono, and some gaming peripherals install vendor-specific display fonts. Building on this, the W3C fingerprinting guidance cites font enumeration as capable of identifying users across sites because the combination of OS default fonts plus application-specific fonts creates a distinctive profile that changes infrequently.1
Why font lists change so slowly
Installed fonts rarely change during a normal browsing session, so a font list remains stable enough for cross-session recognition. This stability is why browser-level font limiting is more effective than clearing cookies or site data. Unlike cookies, which users clear regularly, the font set persists across browser restarts, VPN changes, and even operating system updates, making it one of the most durable passive identifiers available to fingerprinting scripts. A font profile recorded during a first visit can re-identify the same browser months later with high confidence, even when all other session data has been reset.6
Blocking font fingerprinting without breaking text rendering
Blocking font fingerprinting requires restricting the font list that browsers report through canvas measurement, not blocking canvas image exports. Brave with Standard Shields active limits the font probing response to a common baseline subset, making all probe fonts outside the allowed list appear absent regardless of whether they are actually installed.7 Checking which fonts your browser exposes through measureText() reveals whether your Brave protection is actually working, since a protected browser returns a short consistent list rather than a detailed enumeration of every typeface you have installed.
How protections change font visibility
Firefox with privacy.resistFingerprinting enabled restricts font access through a similar mechanism, reporting a standardized font set rather than the actual installed set.8 Conversely, blocking canvas access entirely prevents measureText() from returning meaningful values but breaks font-dependent text layout applications including some CAPTCHA systems, online document editors, and text-rendering tools.8 The Brave approach provides the strongest protection with the smallest compatibility impact because it restricts the probe response rather than the API itself.
When to use this
Use this guide when investigating why the Installed Fonts section of CapyToolkit's Browser Fingerprint Inspector shows a distinctive result, or when evaluating whether your font set contributes significantly to your overall entropy score.
Examples
Font probe detection technique
A script sets ctx.font = "72px monospace" and measures "mmmmmmmmmmlli", baseline width = 320px
The script then sets ctx.font = "72px 'Calibri', monospace" and measures again. If width differs from 320px, Calibri is installed. If identical, Calibri falls back to monospace, absent.
This single comparison runs in under 0.1ms. Repeating it for 200 fonts takes less than 30ms total, invisible in page load timing.
Font set in Brave vs. Chrome on the same Windows machine
Chrome: full font list detected: Segoe UI, Calibri, Cambria, Adobe Fonts (if CC installed), JetBrains Mono (if IDE installed), etc.
Brave with Shields: reduced common subset returned: Segoe UI and common system fonts appear; application-specific fonts like Adobe Fonts and JetBrains Mono return as absent regardless of actual installation
- 1.
W3C, "Mitigating Browser Fingerprinting in Web Specifications," w3.org, 2024. https://www.w3.org/TR/fingerprinting-guidance/
- 2.
BrowserLeaks, "Font Fingerprinting," browserleaks.io, accessed July 2026. https://browserleaks.io/fingerprints/fonts
- 3.
Mozilla Firefox, "Font Fingerprinter Test Page," github.com/mozilla-firefox, 2024. https://raw.githubusercontent.com/mozilla-firefox/firefox/main/browser/base/content/test/protectionsUI/font-fingerprinter.html
- 4.
BotBrowser, "Text Measurement Fingerprinting: Sub-Pixel Tracking," botbrowser.io, 2024. https://botbrowser.io/en/blog/text-measurement-fingerprinting/
- 5.
9to5Mac, "iOS 26 Will Counter One of the Web's Most Invasive Tracking Methods," 9to5mac.com, July 2025. https://9to5mac.com/2025/07/29/with-ios-26-safari-will-counter-one-of-the-webs-most-invasive-tracking-methods/
- 6.
Mozilla Bugzilla, "Bug 1653987 — Restrict CSS font visibility to standard fonts only when privacy.resistFingerprinting is true," bugzilla.mozilla.org, 2021. https://bugzilla.mozilla.org/show_bug.cgi?id=1653987
- 7.
Brave, "GitHub Issue #51331 — Font Fingerprinting Protection (2025)," github.com/brave/brave-browser, 2025. https://github.com/brave/brave-browser/issues/51331
- 8.
Mozilla Firefox, "RFP breaks jigsaw-puzzle-type CAPTCHAs," bugzilla.mozilla.org, Bug 1747674. https://bugzilla.mozilla.org/show_bug.cgi?id=1747674