覆盖率是怎么算的
最后更新 2026-11-02
EN中文
本页目录
「这个字体覆盖了哪些字符」在本站是现算的,不需要重新解析字体、不需要联网、也不需要重跑同步。原因很简单:需要的两份数据都已经在手上,而且都是纯数据。
两个输入
- 字体的码位区间(
cmapRanges):分析字体时从cmap表里抄出来的区间,压成[起, 止, 起, 止, …]的扁平数组存进档案。CJK 字体大约几百段; - 区块表:Unicode 官方
Blocks.txt解析出来的三百多个区块,外加每个区块的「已分配」数,服务端缓存着。
一个在浏览器(本机字体档案)或 API 响应里,一个在 /api/unicode/blocks。两边都是数组与数字,遍历一遍就出结果。
分子:区间与区块求交
对每个区块,数「有多少个码位同时落在字体区间里、又落在这个区块里」。
实现是一趟归并:区块与区间都是升序的,所以用一个游标在区间数组上往前走,每个区块只从上次停下的地方继续看。整体复杂度是「区块数 + 区间数」,不是「区块数 × 区间数」。
(这段代码在 shared/src/fonts/coverage.ts,前端与服务端用的是同一份。)
两个分母
| 数字 | 分母 | 用在哪 |
|---|---|---|
covered / assigned |
区块里已分配的码位数 | 显示在区块行上 |
ratio |
区块的总大小 | 进度条的宽度 |
为什么分开,见 区块与「已分配」:一个 CJK 区块两万多码位里大量是未分配,用总大小当分母,条会显得无关紧要;而用已分配当分母,才能看出「这个字体到底漏了多少该有的字」。
三个限制(如实说明)
- 「映射到非 0 字形」就算覆盖。分析时已经把映射到
.notdef(0 号)的码位滤掉了,但轮廓为空的字形仍然会被算进去——它确实有映射,也确实是那个字形,只是画不出东西。所以「覆盖率 100%」不等于「每个字都好看」。 - 变体序列不算。
cmap里的变体选择符(U+FE0E/U+FE0F那类)不在码位区间里,所以「emoji 的彩色版本」这种能力看不出来。 - 只探测、没解析的本机字体没有覆盖率。这类条目的
cmapRanges是null,页面拿不到区间,只能显示「未分析」。
为什么不用重跑
如果覆盖率要在服务端算好存下来,就得为每个字体 × 每个区块存一行,数据量随字体数线性增长,而算法本身是一次遍历。现算更简单,也不会因为 Unicode 更新区块表而让旧数据对不上。
代价是每次打开页面都要算一遍——实测 353 个区块 × 几百个区间,在浏览器里是毫秒级的事。
延伸阅读
shared/src/fonts/coverage.ts(算法与注释)- Unicode 官方:
Blocks.txt、extracted/DerivedName.txt