字体度量:为什么 100px 不是 100px
给三段文字都设 font-size: 100px,只换 font-family,它们渲染出来的高度却各不相同。原因不在 CSS,而在字体文件:每款字体有一个 em-square(通常 1000 / 2048 个相对单位,概念见字体到底是什么),再在其中标好
ascender / descender / cap-height / x-height——这些度量乘上你的 font-size,才决定了元素真正占的高度(content-area),以及 line-height: normal 到底是多少。
1 · 三个高度的区分
| 名字 | 怎么来的 | 作用 |
|---|---|---|
| em-square | = font-size 本身 (字体的 1000 / 2048 单位缩放到此) | 是 1em 的依据;但不是元素的可见高度 |
| content-area | 字体 ascent + descent,乘 font-size | 元素真正占的内容高度 (背景作用区),可大于 em-square |
| line-height: normal | ascent + descent + line-gap (全在字体里) | 默认行高;不同字体差很多,常见 0.6 ~ 3+ |
三者都随字体而变,只有 em-square 恒等于 font-size。上面 demo 的数字是用 Canvas measureText() 的 fontBoundingBox / emHeight 真实量出来的,line-height:normal 用一个隐藏探针元素量得。
2 · 再下钻一层:content-area 那块「多出来的高」从哪来
上面 demo 里 Catamaran @ 100px 量出来 content-area 是 164px——多出 64px。查看字体文件可以发现:垂直度量其实写了 三套,分别给三个平台 / 用途用——OS/2 表里的 Typo(typo ascender/descender + line-gap,「设计意图」)与
Win(usWin ascent/descent,Windows 裁剪框),还有 hhea 表里的 HHead(Mac)。浏览器拿 Win/HHead 那套撑 content-area,而它常常比 em-square 高——高出来的就是「Win/HHead Ascent − Typo Ascent」(上)和「Win/HHead Descent − Typo Descent」(下)。
下面的 widget 以字体设计师视角调整这三套度量(Win/HHead 一套、Typo 一套、line-gap),实时看 content-area 被撑多高、line-height: normal 怎么算。预置里有一版精确复现原文 Catamaran 的「原生(→164)」与「改窄(→100)」。
为什么要三套?历史包袱:Windows 早年用 usWinAscent/Descent 作行高与裁剪框、Mac 用 hhea 的 ascent/descent,两边标准不一致;后来 OS/2 又补了一套语义更干净的 Typo 度量(设计师真正意图的行距 = Typo Asc − Typo Desc + line-gap)。字体里设了
USE_TYPO_METRICS 标志位,排版才优先用 Typo 那套——没设就退回 Win/HHead,于是同款字在不同系统 / 浏览器的默认行高可能不一样。这就是原文里「Win Ascent 给 Windows、HHead Ascent 给 Mac、设计师一般设成一样」那段话的来历。
line-height: 1 的隐患:unitless 行高是相对 font-size(em-square),不是相对 content-area。很多字体的 content-area 比 em-square 还高,于是 line-height: 1 算出的行框比内容还矮,上下行的字形会相互重叠。原文作者统计自己机器上 1117
款字体里约 1059 款的 normal 行高 > 1(最高 3.378)。正文行高用 1.4 ~ 1.6 这类无单位数字更稳妥,既随字号缩放、又留足行间空间。
这页只讲「高度从哪来」;这些 content-area / 行框怎么在一行里按 vertical-align 摞起来、leading 上下半分、inline-block 的 baseline 在哪——是姊妹系列
Inline 与 inline-block · 拆解 的主题,两边正好接上。至于这页量出的「多出来的那层空白」怎么裁掉(按钮标签 / 标题垂直居中),见text-box-trim 与 strut。
3 · 相关链接
- Deep dive CSS: font metrics, line-height and vertical-align——iamvdo.me · Vincent De Oliveira。本页蓝本。用 FontForge 拆 Catamaran / Arial 的 ascent/descent/line-gap,系统讲解 content-area 与 line-height:normal 怎么从字体度量算出,以及 vertical-align 的行为为何难以预期(后半部分见 inline-block 系列)。
- TextMetrics · MDN——developer.mozilla.org。Canvas
measureText()返回的度量:fontBoundingBoxAscent/Descent、emHeightAscent/Descent、actualBoundingBox… 本页就靠它在浏览器里读真实字体度量。 - Vertical Metrics · Glyphs——glyphsapp.com。「再下钻一层」那节的依据:OS/2 的 Typo(winAscent/Descent vs sTypoAscender/Descender + lineGap)、hhea 三套垂直度量为何并存、USE_TYPO_METRICS 标志位如何决定取哪套作行高,以及跨平台不一致的来历。
- 深度剖析 Baseline 设计原理——zhihu.com · 曾经沧海。用 FontForge 拆 Catamaran:同 font-size:100px 却渲染 164px,多出来的 = Win/HHead Ascent − Typo Ascent(上)+ Win/HHead Descent − Typo Descent(下)。本页「三套度量」widget 的预置即复现其 110/54 → 164 的算例。baseline 位置那半截见姊妹系列 inline-block。
- 深入理解 CSS:字体度量、line-height 和 vertical-align(中译)——zhihu.com · 方应杭 译。iamvdo 原文的中文翻译,术语对照(em-square / content-area / virtual-area / leading)读起来更顺。