How coverage is calculated
Last updated 2026-11-02
On this page
"Which characters does this font cover" is computed on the fly here: no re-parsing, no network call, no re-running the sync. The reason is simple — both inputs are already available and both are plain data.
The two inputs
- The font's code point ranges (
cmapRanges): ranges copied out of thecmaptable during analysis, compressed into a flat[start, end, start, end, …]array and stored in the archive. A CJK font lands in a few hundred ranges; - The block table: the 350-odd blocks parsed from Unicode's
Blocks.txt, each with its assigned count, cached on the server.
One lives in the browser (local font archive) or an API response, the other behind /api/unicode/blocks. Both are arrays and numbers; one pass produces the result.
Numerator: intersecting ranges with blocks
For each block, count code points that fall both inside the font's ranges and inside the block.
The implementation is a single merge pass: blocks and ranges are both ascending, so a cursor walks the range array forwards, and each block continues where the previous one stopped. The cost is "blocks + ranges", not "blocks × ranges".
(The code is in shared/src/fonts/coverage.ts; client and server share it.)
Two denominators
| Figure | Denominator | Used for |
|---|---|---|
covered / assigned |
assigned code points in the block | the number on the row |
ratio |
the block's total size | the width of the bar |
Why two: see Blocks and "assigned". A CJK block spans 20,000 code points, most unassigned; scaling by total size makes the bar look irrelevant, while scaling by assigned shows how many real characters the font is missing.
Three limits, stated honestly
- "Mapped to a non-zero glyph" counts as covered. Analysis already drops code points mapped to
.notdef(glyph 0), but a glyph with an empty outline still counts — it has a mapping, it is that glyph, it just draws nothing. So "100% coverage" does not mean "every character looks good". - Variation sequences are not counted. Variation selectors (
U+FE0E/U+FE0Fand friends) live in a separatecmapsubtable and are not part of the ranges, so "does it have the colour emoji" is invisible here. - Local fonts that were only probed have no coverage. Their
cmapRangesisnull, the page has no ranges to work with, and it shows "not analysed".
Why not precompute
Precomputing would mean storing one row per font × block, growing linearly with the collection, to replace a single traversal. Computing live is simpler and never goes stale when Unicode updates its blocks.
The price is recomputing on every page view — measured in milliseconds for 353 blocks against a few hundred ranges.
Further reading
shared/src/fonts/coverage.ts(algorithm and comments)- Unicode:
Blocks.txt,extracted/DerivedName.txt