字体基础知识:分类、度量与编码

做字体渲染这些年,我最大的感受是:不懂字体分类和度量,写出来的渲染代码迟早要翻车。刚入行时我总觉得「不就是画几个字嘛」,直到被衬线体的 hinting 坑过一次,才老老实实回来补基础。

这一章咱们把三个核心概念讲透:字体的分类、度量体系、字符编码。别小看这些,它们是后面所有渲染算法的基础。

字体的分类

字体分类这事儿,说白了就是看笔画末端有没有「小爪子」。我习惯把字体分成三大类:

衬线体(Serif)

笔画末端有装饰性的「衬线」,像小爪子一样。典型代表:Times New Roman、宋体。

  • 特点:横细竖粗,衬线引导视线横向流动
  • 适用场景:印刷正文、长文阅读
  • 渲染难点:小字号时衬线容易糊成一团,需要 hinting 干预
我的经验:做电子书渲染时,衬线体在 12px 以下必须开 hinting,否则衬线会变成「毛刺」。我踩过这个坑——用户投诉说「字像长了毛」。

无衬线体(Sans-serif)

没有装饰性衬线,笔画粗细均匀。典型代表:Helvetica、微软雅黑、思源黑体。

  • 特点:简洁现代,屏幕可读性好
  • 适用场景:UI 界面、网页正文、小字号显示
  • 渲染优势:笔画简单,抗锯齿效果好处理

等宽字体(Monospace)

每个字符占据相同宽度。典型代表:Consolas、Source Code Pro、Courier New。

  • 特点:字符对齐整齐,适合代码和表格
  • 适用场景:代码编辑器、终端、ASCII 艺术
  • 渲染注意:等宽不代表所有字符视觉宽度一致,i 和 w 的留白差异需要微调
分类 典型字体 主要用途 渲染难度
衬线体 Times New Roman、宋体 印刷、长文 高(小字号需 hinting)
无衬线体 Helvetica、微软雅黑 屏幕 UI、网页 中
等宽字体 Consolas、Source Code Pro 代码、终端 低(但对齐要求高)

字体的度量

字体度量是渲染引擎的「坐标系」。你想想看,如果不知道基线在哪,字符就全飘在天上了。我画个图帮你理解:

字体度量结构图 上升线 (Ascender) 大写线 (Cap Height) x-高度线 (x-Height) 基线 (Baseline) 下降线 (Descender) H x p 上升部分 下降部分 x-高度

核心度量术语

  • 基线(Baseline):所有字符的「地面」,大部分字符底部对齐于此
  • 上升部(Ascender):从基线到最高点的距离,包含大写字母和上伸部分
  • 下降部(Descender):从基线到最低点的距离,如 g、j、p 的下伸部分
  • x-高度(x-Height):小写字母 x 的高度,影响小字号可读性
  • 大写高度(Cap Height):大写字母的高度

关键点:渲染时所有字符的位置都基于基线计算。上升和下降决定了行高,x-高度决定了小字号的清晰度。我见过有人把上升和下降设反了,结果文字上下颠倒——嗯,调试时还挺好笑的。

度量在渲染中的实际应用

举个例子,你要计算两行文字之间的行距:

// 伪代码:计算行高
lineHeight = ascender - descender + lineGap
// 下一行起始 Y 坐标
nextLineY = currentLineY + lineHeight

我曾经在某个项目中,直接用了字体的 emSize 作为行高,结果行间距大得离谱。后来才发现,emSize 是设计空间,不是实际行高。实际行高要用 ascender - descender 算。

字符编码

字符编码是计算机和字体之间的「翻译官」。没有编码,字体就不知道你要画哪个字。

ASCII

最基础的编码,128 个字符,包含英文字母、数字、标点和控制字符。一个字节存一个字符。

  • 范围:0x00 - 0x7F
  • 局限:只能表示英文,中文?不存在的
  • 渲染意义:现代字体渲染基本不直接处理 ASCII,但它是所有编码的基础

Unicode

统一码,目标是给全世界所有字符一个唯一的编号。目前已经超过 14 万个字符。

  • 编码方式:UTF-8、UTF-16、UTF-32
  • UTF-8:变长编码,1-4 字节,兼容 ASCII,Web 和文件系统最常用
  • UTF-16:2 或 4 字节,Windows 和 Java 内部使用
  • UTF-32:固定 4 字节,处理简单但浪费空间
编码 字节数 兼容 ASCII 常见场景
UTF-8 1-4 是 Web、Linux、JSON
UTF-16 2 或 4 否 Windows、Java、.NET
UTF-32 4 否 内部处理、ICU 库
避坑指南:我曾经在处理中文文本时,误把 UTF-8 的中文字符按单字节读取,结果每个汉字被拆成 3 个乱码字符。后来我养成了一个习惯:所有文本输入输出都明确指定编码,绝不依赖系统默认。

编码与字体渲染的关系

渲染引擎拿到 Unicode 码点后,需要做两件事:

  1. 查表:通过 cmap 表(字符映射表)找到对应的字形索引
  2. 取形:根据字形索引从 glyf 表(字形数据表)中取出轮廓数据

说白了,Unicode 是「门牌号」,cmap 表是「地图」,glyf 表是「房子」。没有门牌号,你就找不到房子。

我的习惯:调试字体渲染时,我第一步总是检查 cmap 表是否正确映射。如果某个字符显示为「豆腐块」(缺字方块),90% 是 cmap 表没找到对应条目。

本章小结

字体分类决定了渲染策略,度量体系决定了排版布局,字符编码决定了字符映射。这三者构成了字体渲染的「三角基石」。后面讲 TrueType 和 OpenType 时,你会发现所有技术细节都绕不开这些基础概念。