cmap 表:字符怎么找到字形

最后更新 2026-11-02

EN中文
本页目录

字体里的字形是按编号存的,不是按字符存的。cmap 就是那张对照表:给它一个字符的码位,它告诉你该用第几号字形。所有「这个字体认不认这个字」的问题,答案都在这张表里。

表里有什么

三层结构:

  1. 表头:版本号、子表数量;
  2. 若干条编码记录:platformID + encodingID + 子表位置;
  3. 若干张子表,每张用不同的压缩方式存「码位 → 字形索引」。

一个字体常常带好几张子表:一张给 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 只回答「映射到第几号字形」。有三种情况会让它看起来在撒谎:

  1. 映射到 0 号字形:0 号永远是 .notdef,也就是豆腐块。有些字体把没做的字符全指到 0,而不是干脆不写这条映射。
  2. 字形存在但轮廓为空:映射指向一个真实存在的字形,但它的轮廓是空的(或者全是零长度路径)。浏览器画不出东西,就会回退到下一个字体——于是你看到的是别的字体画的,而不是「这个字体有」。
  3. 变体选择符:字符本身映射正常,但你要的变体(U+FE0F 之类)在 format 14 里没有,结果走了默认字形。

本站的字符抽屉里对第 2 种情况有提示,因为「看起来有」和「确实是这个字体画的」是两件事。

本站怎么用它

  • 分析字体时只读 cmap,把里面的映射压成码位区间存进档案(cmapRanges)——一个全量 CJK 字体大约几百段,几十 KB 以内;
  • 覆盖率页用这些区间判断「某个码位在不在」;
  • 分母不是区间长度,而是已分配码位数:一个 CJK 区块有两万多个码位,其中大量是未分配的,用区块总长算会把覆盖率算成个位数百分比。

一处容易误会的地方

cmap 里的区间是「有映射的码位」,不代表这些字符有好看的形状。字体可以给一个码位映射一个极简的占位轮廓。判断一个字「长什么样」,得看轮廓表(glyf / CFF),不是 cmap。

延伸阅读

  • OpenType 规范:cmap 表(每种子表格式的二进制布局)
  • OpenType 规范:cmap 的符号字体与 platform / encoding 组合表