cmap 表:字符怎么找到字形
最后更新 2026-11-02
EN中文
本页目录
字体里的字形是按编号存的,不是按字符存的。cmap 就是那张对照表:给它一个字符的码位,它告诉你该用第几号字形。所有「这个字体认不认这个字」的问题,答案都在这张表里。
表里有什么
三层结构:
- 表头:版本号、子表数量;
- 若干条编码记录:
platformID+encodingID+ 子表位置; - 若干张子表,每张用不同的压缩方式存「码位 → 字形索引」。
一个字体常常带好几张子表:一张给 BMP(基本多文种平面),一张给全 Unicode,一张给老式符号编码。程序按自己的平台与能力挑一张来用。
常见的格式
| 格式 | 用途 |
|---|---|
| 0 | 256 字节的查找表,只覆盖单字节编码,老字体才有 |
| 4 | BMP 分段映射,最常见的格式;用「段」存连续区间,省空间 |
| 6 | 一段连续码位的直接查找表 |
| 12 | 分段覆盖,支持 32 位码位,含 BMP 之外的字符(emoji、CJK 扩展) |
| 13 | 多对一,把一大片码位都指向同一个字形(用于「兜底」字体) |
| 14 | Unicode 变体序列:指定某个字符的某个变体字形 |
平台组合里最常遇到的是 Windows 的 3/1(BMP)与 3/10(全 Unicode),以及 Unicode 平台的 0/3、0/4。
符号字体与那个「F000 偏移」
有些老字体是「符号字体」:编码记录里 platform 3、encoding 0,子表把 ASCII 码映射到图形上,但规定实际使用时码位要加 0xF000。于是同一个字形,有人用 U+0041 取到,有人要用 U+F041。
这不是 bug,是那个年代的约定。今天新建的字体基本不再这么做,但老字体(尤其是图标字体)里仍然常见。
「有码位」不等于「画得出」
cmap 只回答「映射到第几号字形」。有三种情况会让它看起来在撒谎:
- 映射到 0 号字形:0 号永远是
.notdef,也就是豆腐块。有些字体把没做的字符全指到 0,而不是干脆不写这条映射。 - 字形存在但轮廓为空:映射指向一个真实存在的字形,但它的轮廓是空的(或者全是零长度路径)。浏览器画不出东西,就会回退到下一个字体——于是你看到的是别的字体画的,而不是「这个字体有」。
- 变体选择符:字符本身映射正常,但你要的变体(
U+FE0F之类)在 format 14 里没有,结果走了默认字形。
本站的字符抽屉里对第 2 种情况有提示,因为「看起来有」和「确实是这个字体画的」是两件事。
本站怎么用它
- 分析字体时只读
cmap,把里面的映射压成码位区间存进档案(cmapRanges)——一个全量 CJK 字体大约几百段,几十 KB 以内; - 覆盖率页用这些区间判断「某个码位在不在」;
- 分母不是区间长度,而是已分配码位数:一个 CJK 区块有两万多个码位,其中大量是未分配的,用区块总长算会把覆盖率算成个位数百分比。
一处容易误会的地方
cmap 里的区间是「有映射的码位」,不代表这些字符有好看的形状。字体可以给一个码位映射一个极简的占位轮廓。判断一个字「长什么样」,得看轮廓表(glyf / CFF),不是 cmap。
延伸阅读
- OpenType 规范:
cmap表(每种子表格式的二进制布局) - OpenType 规范:
cmap的符号字体与 platform / encoding 组合表