Back to Knowledge Hub
Platform

How CJK Font Rendering Reveals Your Real OS in Browser Fingerprints

Chinese, Japanese, and Korean font families, fallback chains, and text metrics can reveal the host OS. Learn what differs and what consistent output needs.

BotBrowser Team

Documentation

Prefer the maintained product doc?

This article has a matching page in the docs center. Use the docs for the canonical setup flow, current flags, and long-term reference.

Why CJK text reveals the host platform

Chinese, Japanese, and Korean text is one of the quickest ways for a page to learn which operating system a browser really runs on. Each platform ships its own CJK font families, resolves missing fonts through its own fallback chain, and draws glyphs with its own text-rendering stack. A profile that looks consistent for Latin text can still show the host platform as soon as a page measures or draws East Asian characters.

The reason is simple. Latin fonts such as Arial or Helvetica exist on several systems, so a font list that contains them says little. CJK fonts are large, tied to a vendor, and installed according to the language packs of the system. A list that contains Microsoft YaHei points to Windows. A list that contains PingFang SC points to macOS. A list that contains Noto Sans CJK and WenQuanYi points to a typical Linux desktop or server.

The sections below look at what CJK rendering exposes, why common host-side fixes leave a measurable gap, and what consistent cross-platform output requires. They also show how to choose a --bot-fonts mode for a CJK workflow and how to check the result with a short Canvas width comparison. For the broader picture of font signals, see font fingerprinting and cross-platform text consistency.

CJK font families by platform: Windows, macOS, and Linux examples

CJK font families by platform

The lists below are representative examples, not complete inventories. Real installations vary with the operating system version, the installed language packs, and any fonts the user or an administrator added. Use them to understand which families usually belong to which platform, not as a checklist that every machine must satisfy.

Windows commonly provides these CJK families.

  • Simplified Chinese: Microsoft YaHei, SimSun, SimHei, FangSong, KaiTi, NSimSun
  • Traditional Chinese: Microsoft JhengHei, MingLiU, PMingLiU
  • Japanese: Yu Gothic, Meiryo, MS Gothic, MS Mincho, MS PGothic
  • Korean: Malgun Gothic, Batang, Dotum, Gulim, Gungsuh

macOS commonly provides these CJK families.

  • Simplified Chinese: PingFang SC, STHeiti, STSong, STKaiti, STFangsong
  • Traditional Chinese: PingFang TC, PingFang HK, LiSong Pro
  • Japanese: Hiragino Sans, Hiragino Kaku Gothic, Hiragino Mincho
  • Korean: Apple SD Gothic Neo, AppleMyungjo, Nanum Gothic

Linux distributions commonly provide these CJK families, depending on which packages were installed.

  • All three languages: Noto Sans CJK (SC, TC, JP, and KR variants) and Noto Serif CJK
  • Chinese: WenQuanYi Micro Hei, WenQuanYi Zen Hei
  • Japanese: IPAGothic, IPAMincho, Takao Gothic
  • Korean: UnBatang, UnDotum

The practical consequence is a set of mismatches that a reader can recognize on sight. A profile that claims Windows with a Chinese locale but reports no Microsoft YaHei or SimSun is inconsistent, because a Chinese Windows installation ships them by default. A macOS profile aimed at Japan that lacks Hiragino Sans is inconsistent in the same way. A Linux machine that reports Windows fonts next to Apple fonts matches no real installation at all, since those families never come from one vendor.

Region matters inside Chinese as well. Simplified Chinese is the norm in mainland China, while Traditional Chinese is used in Taiwan and Hong Kong, and each platform has separate families for them, such as PingFang SC compared with PingFang TC and PingFang HK on macOS, or Microsoft YaHei compared with Microsoft JhengHei on Windows. A profile that claims a Taiwan locale should therefore carry the Traditional Chinese families, not only the Simplified ones.

Language matters as much as platform. A Windows machine configured for English may report few CJK fonts, while a machine configured for Chinese reports many. For this reason a CJK locale, a CJK font list, and the claimed operating system have to agree. If one of the three is missing or points elsewhere, the combination looks unusual even when each value on its own is plausible.

Fallback chains and text metrics

When a page asks for a font family that is not installed, the browser does not stop. It walks a fallback chain, which is the ordered list of families it tries until one covers the characters in the text. The CSS Fonts specification describes how generic families and per-character fallback work, and each platform fills those mechanisms with its own defaults. The result is that the same CSS can resolve to different fonts on different systems.

Typical Chinese fallback chains look like this.

  • On Windows, Chinese text usually resolves to Microsoft YaHei and then SimSun.
  • On macOS, it usually resolves to PingFang SC and then STHeiti.
  • On Linux, it usually resolves to a Noto Sans CJK or WenQuanYi family, depending on what is installed.

Page authors usually write a font stack, for example a macOS family first, a Windows family second, and a generic family last. The browser evaluates the stack from left to right for each character and uses the first family that is installed and covers the character. The order in the page's own CSS therefore decides which installed family becomes visible, and a stack written for several platforms naturally shows a different result on each of them.

Consider a page that asks for font-family: "Nonexistent Font", sans-serif and displays Chinese text. Nothing in the page names a platform, yet the final glyphs come from a different family on each system, and the visible shapes and spacing change with them. A page does not need a list of installed fonts to notice the difference. It only needs to render text and observe the result.

The language of the text adds another layer. Chinese, Japanese, and Korean share many Han characters, but the preferred glyph shapes differ by language. Browsers use the declared language of the content and the language settings of the system to choose between regional variants. A locale that claims Japanese while the fallback produces Chinese glyph shapes is one more inconsistency to avoid.

Beyond availability, CJK fonts produce measurable differences.

  • Glyph widths: individual characters, and especially mixed Latin and CJK strings, have different advance widths in different families.
  • Line height: the default spacing between lines depends on the vertical metrics of the chosen family.
  • Spacing rules: punctuation, kerning, and character spacing follow different conventions in Chinese, Japanese, and Korean typography.
  • Hinting and rasterization: the pixel-level look of small text depends on the font's hinting data and on DirectWrite on Windows, Core Text on macOS, or FreeType on Linux.

These values feed layout. Calls such as getBoundingClientRect() and getComputedStyle() return numbers that depend on the font that was actually used, and Canvas measureText returns the width of a string in the current font. MDN documents measureText as a way to obtain text metrics, which is why it is also useful for a simple check: if the width of a string changes after a family is named, the family was found, and if it does not change, the browser fell back.

Why host-side fixes leave a measurable gap

Three approaches are common when an operator wants CJK output that differs from the host. Each one solves a part of the problem and leaves another part open. Understanding the gap explains why consistent output depends on aligning the font list and the rendering result together.

The first approach is installing the target platform's CJK fonts on the host. On a Linux server this can mean adding Microsoft core fonts, CJK packages, or individual font files. It works for some pages, but it has costs.

  • It changes the operating system, so every browser instance on the machine sees the new fonts, not only the instance that needs them.
  • Installing Windows and macOS families together produces a list that matches no real platform.
  • Rendering still goes through the host's text stack, such as FreeType on Linux, not the stack of the platform being represented.
  • Keeping installed fonts identical across many servers adds operational work and licensing questions.

The second approach is web font injection. A page or a script adds fonts through CSS so that text draws with a chosen family. This controls how some text looks, but it does not change what the system reports. A common way to detect a font is to measure text with and without the family and compare the widths. An injected web font can be rendered while the system font list still shows the host's real fonts, so the two views disagree.

The third approach is an extension that intercepts font-related API calls and changes the reported list. The list can be edited, but the pixels cannot. If the list claims Microsoft YaHei while the text is actually drawn by a FreeType font from the host, a Canvas measurement shows a width that does not belong to Microsoft YaHei. The reported value and the observed value disagree, and the disagreement is measurable.

All three approaches share one weakness. They change one layer, either installation, page styling, or the reported list, and leave the others as they were. Consistent CJK output needs enumeration, fallback behavior, and rendering to move together, because a page can compare any two of them.

Choosing a font mode for CJK work

BotBrowser documents font handling through --bot-fonts, which has three modes. The profile carries a font bundle for its target platform, and the mode decides how that bundle relates to the host's fonts. The BotBrowser font documentation describes the profile bundle as the complete set of standard fonts for the target platform, and says that enumeration results follow what that platform would report.

  • --bot-fonts=profile uses the font bundle embedded in the profile and is the default. Use it when the profile's inventory and fallback behavior should stay authoritative.
  • --bot-fonts=expand uses the profile fonts and lets the workflow fall back to host fonts for characters the bundle does not cover. Use it only when the workflow intentionally allows that fallback.
  • --bot-fonts=real uses the host's real fonts. Use it for comparison or testing, and expect host-dependent output because the profile bundle is not applied.

For most CJK workflows, profile is the right starting point. It keeps font availability and fallback tied to the profile's platform, so a Windows profile with a Chinese locale reports Microsoft YaHei and SimSun even when the host is a Linux server without those fonts. That also removes the need to install CJK fonts on the host for the sake of the profile.

A font mode is only one part of a coherent regional setup. Pair a CJK profile with the locale, language list, and timezone of the same region, as described in timezone, locale, and language consistency. The flags are listed in the CLI flags reference, together with the plan tier of each flag. Three examples follow.

chrome --bot-profile="profiles/win11-zh-cn.enc" \
       --bot-fonts=profile \
       --bot-locale=zh-CN \
       --bot-languages=zh-CN,zh,en \
       --bot-timezone=Asia/Shanghai
chrome --bot-profile="profiles/win11-ja.enc" \
       --bot-fonts=profile \
       --bot-locale=ja-JP \
       --bot-languages=ja,en \
       --bot-timezone=Asia/Tokyo
chrome --bot-profile="profiles/win11-ko.enc" \
       --bot-fonts=profile \
       --bot-locale=ko-KR \
       --bot-languages=ko,en \
       --bot-timezone=Asia/Seoul

The expand mode deserves a careful decision. It is useful when a page contains rare ideographs, symbols, or scripts that the profile's bundle does not cover, because the host can supply a glyph instead of leaving a box. The cost is that those characters now come from the host's fonts, so the output for them depends on the machine. Choose expand only when complete coverage matters more than strict agreement with the profile's platform, and keep profile for workflows where consistency is the goal.

The profile itself matters. A profile captured from an installation that already used the target locale carries the CJK families that came with that configuration, so prefer profiles captured from a matching locale installation. A profile captured from an English-only machine will not contain the full Chinese or Japanese set, and no flag can add a bundle that the profile does not have. When a workflow needs both Japanese and Chinese fonts, use a profile captured from a system that had both language packs. For running one profile across operating systems in general, see cross-platform browser profiles.

Checking the result and knowing the limits

A simple width comparison confirms basic font availability. Measure a string that mixes Latin letters and CJK characters in the font you expect, then measure the same string in a fallback font such as monospace. If the two widths differ, the named family was found. If they match, the browser fell back. Use a mixed string because many CJK families give every ideograph the same advance, so a string of ideographs alone can produce equal widths even for different fonts. The following snippet can be pasted into the developer console of a test page.

const ctx = document.createElement('canvas').getContext('2d');
const sample = 'Hello 你好 mmmlli';
ctx.font = '16px monospace';
const baseline = ctx.measureText(sample).width;
ctx.font = '16px "Microsoft YaHei", monospace';
const candidate = ctx.measureText(sample).width;
console.log({ baseline, candidate, found: candidate !== baseline });

Run the same check for a few families that belong to the profile's target platform and for a few that do not. A Windows Chinese profile should find Microsoft YaHei and SimSun and should not find PingFang SC. A macOS Japanese profile should find Hiragino Sans and should not find Meiryo. Repeat the check on at least two host operating systems with the same profile. If the answers change with the host, the host's fonts are leaking into the result, and the first thing to confirm is that --bot-fonts=profile is set.

Keep the meaning of the result in proportion. A width comparison shows availability. It is not proof that every glyph, every line height, and every rasterization detail matches a real machine of the source platform. Treat it as a first check, and complement it with a visual review of a CJK-heavy page in the language you care about. A short review routine helps.

  • Confirm that the profile was captured from the locale and platform you intend to represent.
  • Confirm that locale, language list, and timezone point to the same region as the profile.
  • Run the width comparison for families that belong to the target platform and for families that do not.
  • Open a page with a lot of Chinese, Japanese, or Korean text and look for missing glyphs or boxes.
  • Repeat on a second host operating system with the same profile and compare the answers.

Unexpected results usually have a short list of causes. CJK characters that appear as boxes suggest that the profile does not include the CJK font bundle for that language, so choose a profile version that does. Text widths that differ between two hosts suggest that the BotBrowser version or the profile differs, because font updates between versions can change metrics. Inconsistent spacing in mixed CJK and Latin text usually points to fallback differences, so confirm that the profile's font configuration covers both scripts. Rare characters that render differently may fall through to host fonts. You can report them so they can be added to future font bundle updates, or use expand if the workflow intentionally allows host fallback.

BotBrowser supports --bot-fonts=profile, which uses the font bundle embedded in the profile, so the reported CJK font availability, enumeration, and fallback follow the profile's target platform instead of the fonts installed on the host. For a CJK workflow, this lets you check that a Chinese, Japanese, or Korean locale profile stays consistent when it runs on a different host operating system. BotBrowser cannot guarantee that a site's own checks accept the identity, cannot replace a profile captured from a matching locale installation, and cannot control a page's own fonts, its CSS fallback choices, or the platform's server-side behavior.

Public sources

#Cjk#Fonts#Chinese#Japanese#Rendering#Cross-Platform#Font Fingerprint#Browser Fingerprint

Take BotBrowser from research to production

The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.