Library / SDK
KonghaYao/cn-font-split avatar
KonghaYao/cn-font-split

cn-font-split, and the problem of shipping a CJK font to a browser

划时代的字体切割工具,CJK与任何字符!支持 otf、ttf、woff2 字体多线程切割,完美地细颗粒度地进行包大小控制。A revolutionary font subetter that supports CJK and any characters! It enables multi-threaded subset of otf, ttf, and woff2 fonts, allowing for precise control over package size.

1,254 stars49 forksRustApache-2.0

At a glance

What is it?
cn-font-split is a Rust font subsetter built for the Chinese web font project, splitting otf, ttf and woff2 files into small pieces with a fine grained control over package size. The seventh major version moved the core to native Rust and shipped it as WebAssembly, FFI and a gRPC service, and the upstream stack it replaced is documented in the project description.
Who is it for?
Adopt cn-font-split if you serve CJK or other large script web fonts and need to control the transferred bytes precisely, because the subsetting is done natively in Rust with harfbuzz and the output is identical across platforms, which is what makes it usable in a build pipeline. Do not adopt it if you only need a one-off subset of a Latin font, since a general purpose subsetter will do that with less machinery.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 133 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: a CJK font is too big to ship whole

cn-font-split is the font splitting tool used by the Chinese web font project, and its purpose is stated in the project description as splitting large font packages into versions suited to distribution over the network, using high performance techniques.

The problem it solves is specific and severe. A CJK font contains tens of thousands of glyphs, because the character set is large, and a single font file of that kind is measured in megabytes. A page that wants to display Chinese text has two bad options: ship the whole file and pay for it on every page load, or use a subsetting service that cuts a font down to the characters a given page actually needs. The second is the only workable one, and the quality of the subsetting determines whether the page looks right.

The tool does not only handle Chinese. The description says it is not limited to Chinese, and that Chinese, Japanese and Korean text, minority scripts and Arabic all have optimisations, and that it can split intelligently according to the characters actually present in a given font package.

That last point is the one that separates a real subsetter from a script that copies a character list. A tool that knows which characters are in the font, and how the font's own layout tables relate them, can produce subsets that are not merely smaller but correct, with the right glyphs for the right contexts.

The audience is web teams serving text in large scripts, and the historical context is telling: the project description says that after seven major versions of font research and code iteration, the technique is fully applied in the project's own website, and it claims a leap in both loading speed and efficiency for Chinese fonts on the web.

The licence is Apache-2.0, and the default branch is release.

A Rust core with WebAssembly, FFI and gRPC front ends

The implementation strategy is stated as WebAssembly or Rust FFI, running the subsetting natively, with build times in the seconds. The claim is precise enough to check: a 2MB font takes 50ms.

The platform story follows from that choice. The project insists on the web platform as the base, which gives very broad compatibility: browsers, WASI, Linux, macOS and Windows all run it, and Rust, JavaScript and Python can all reuse it.

The second property is the one that matters most for a build tool. The output is consistent across platforms, so running cn-font-split on a developer laptop and running it in CI produce the same result. For a tool whose output is committed to a repository or shipped to a CDN, that determinism is not a nicety, because a different build machine producing different bytes would invalidate every cache and every hash you published.

The workspace layout shows how many front ends there are. The Cargo workspace members include a wasmer package, an ffi package, a grpc package and a wasm-edge package, alongside the shared crates for font utilities, unicode language data, the protobuf definitions and the unicode range handling.

The gRPC package is worth noting because it implies a service model. The dependencies include prost for the protobuf runtime and tonic-build as a build dependency, and the proto crate is a workspace member. That means the subsetter can run as a separate process and be called over gRPC, which is what you want when the subsetting is expensive enough to be worth a long lived service, and it is why the FFI package matters too, since an in-process library is the other way to get the same result.

The release profile sets link time optimisation on, which is the right call for a binary whose whole claim is speed.

What the new version added, feature by feature

The project description lists eleven features of the current version, and they are a good map of what the tool does now.

Native build support with speed in the seconds, the 2MB font in 50ms figure. A complete test and release process. A more human readable source layout, with the stated aim of making maintenance less painful. Dependency checks and refactoring for security versions. More controllable splitting, supporting fine grained character splitting. OTF format font packaging, with complex glyph rendering. Font preview image generation. Support for Nodejs, Deno, Bun and the browser, with consistent build artefacts across them. Splitting of any character present in the package, not only Chinese. Automatic recognition of variable font weights. And OpenType feature support, where the numbers are given: 95 supported, 9 partially supported and 20 awaiting testing.

That last line is the most useful number in the description, and it is the one to check before you rely on the tool. A font that uses an OpenType feature the subsetter does not understand may subset into something that renders differently, and the table of 20 features still awaiting testing is exactly the list of things to look at.

The preview image generation is the feature with the widest appeal outside the CJK case, because a split font is hard to verify by eye. If the tool can render the text to an image, you can check a subset before you ship it, which turns a class of silent failures into something you can see.

The engine underneath: harfbuzz in Rust, and a hand written text SVG renderer

The credits section names the components that do the real work.

The core plugin is Harfbuzz, the text shaping engine, which provides professional grade font subsetting and font drawing. The project uses the Rust version, harfbuzz_rs_now, embedded into the Rust ecosystem, and credits it with an ideal subsetting result.

The second is the Rust opentype crate, credited with font information extraction capability, providing a solid foundation for font features and the splitting algorithm.

The third is the Rust cross project, for building cross platform artefacts.

On top of that, the project built its own Rust tool to construct a text SVG engine and render text images independently, which is what the preview image generation uses. Writing that rather than shelling out to a system tool is what makes the output identical on every platform.

Two more acknowledgements are unusual enough to be worth repeating. The description thanks Qwen and GPT-4o for information and code help during the Rust rewrite, and says the main project was completed in 20 days because of that help, and it thanks Claude 3.5 for writing most of the test cases.

That is a genuine statement about how the seventh version came about, and it is the sort of thing a project would only write down if it wanted readers to understand the velocity of the rewrite and the reason the test suite exists in the shape it does.

The old stack, and why the project replaced it

The description also lists the projects older versions used, and reading that list explains the shape of the current one.

In version 6.0, opentype.js was the second parsing engine, mainly handling feature relationship judgement and converting text to SVG, and it gave a lot of support for rendering. In 6.0, @napi-rs/ttf2woff2 let the Nodejs and Bun platforms compress font files at native speed. The wawoff2 project compiled Google's woff2 conversion into wasm, which gave a simple API for font compression, but because wawoff2 exports the wasm embedded in js, that badly affected js bundling and use, so the project rebuilt and published its own version. And multithreading in 6.0 used the workerpool solution, which made it much faster.

The pattern is a JavaScript and wasm toolchain that was fast but awkward, and a version 7 that moved the whole thing to Rust. The complaints in that list are specific and each maps to something in the new design: the wasm embedded in js became a native Rust core with a WebAssembly target; the second parsing engine became the Rust opentype crate; the workerpool threading became the rayon dependency in the Cargo manifest.

That is a coherent rewrite story, and it is also the honest caveat for anyone adopting the project today: the current version is young.

vite-plugin-font, the language bindings and the repository layout

The most practical piece of news for a front end team is the compiler plugin. The description announces it in bold: vite-plugin-font, supporting Vite, Nuxt, Next, Webpack and Rspack, for quick embedding into your front end toolchain.

That plugin is the difference between a subsetter you run by hand and a subsetter that runs in your build. A font that is split at build time, with the split driven by the characters your pages actually contain, is the whole point of the project, and a plugin is how that becomes automatic rather than a weekly chore.

The language bindings are documented separately, with a JavaScript document in the packages/ffi-js directory and a Python document in packages/ffi-py, and the Python one carries a note inviting readers to improve it, with an invitation to open issues or pull requests if they want support for another language.

The repository layout is a mixed Rust and TypeScript workspace. There is a crates directory for the shared Rust crates, a packages directory for the wasmer, ffi, grpc and wasm-edge packages, a src directory for the binary, a test directory, a script directory, an assets directory, a Cross.toml for the cross compilation configuration, a rustfmt.toml, a pnpm workspace configuration and a publish-all.sh script. The root package.json is a private root package with the pnpm overrides, and the preinstall hook runs a fix script and then enforces pnpm with only-allow.

Those overrides are worth a look if you adopt the packages. They pin several transitive dependencies to specific ranges, including cross-spawn, brace-expansion, minimatch, picomatch, serialize-javascript, lodash and ajv, which is the sort of security pinning a package that ships to many projects has to do.

Editorial conclusion

Adopt cn-font-split if you serve CJK or other large script web fonts and need to control the transferred bytes precisely, because the subsetting is done natively in Rust with harfbuzz and the output is identical across platforms, which is what makes it usable in a build pipeline. Do not adopt it if you only need a one-off subset of a Latin font, since a general purpose subsetter will do that with less machinery. Before you commit, read the OpenType feature support table, because 20 features are listed as awaiting testing and a font that relies on them will not subset cleanly, and check the vite-plugin-font integration if you already build with Vite, Nuxt, Next or Webpack.

Frequently asked questions

What is cn-font-split used for?

It splits large font files into small pieces suited to distribution over the network, so a web page only serves the glyphs it needs. It handles otf, ttf and woff2, is optimised for Chinese, Japanese and Korean text as well as other scripts, and can split any character present in the font.

Which languages and platforms does cn-font-split support?

The project lists WebAssembly and Rust FFI implementations, with the core running natively in the browser, under WASI, on Linux, macOS and Windows, and with Rust, JavaScript and Python able to reuse it. JavaScript and Python documentation live in the ffi-js and ffi-py packages.

How fast is the subsetting?

The project description claims native build support with build times in the seconds, and gives the figure of 50ms for a 2MB font. The output is stated to be consistent across platforms, so a local build and a CI build produce the same result.

Which OpenType features does cn-font-split support?

The project publishes a support table listing 95 features as supported, 9 as partially supported and 20 still awaiting testing, with the table in the test package of the repository.

Can I use cn-font-split inside my Vite or Webpack build?

Yes, through the vite-plugin-font package, which the project says supports Vite, Nuxt, Next, Webpack and Rspack. There is also a gRPC package and an FFI package in the Cargo workspace for calling the subsetter from other processes or languages.

Official sources

  1. KonghaYao/cn-font-split on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/konghayao-cn-font-split.svg)](https://hysenlabs.com/projects/konghayao-cn-font-split)

Was this useful?