深入研究CSS字体度量及CSS 盒子

头图

这张图展示的是8种不同的字体,其中第一、第二个分别为 font-awesome图标、自定义的字体图标,其余字体依次为AvenirTrebuchet MSArialHelveticaHiragino Sans GBSTXihei源代码在这里。这些字符的font-size:100px,但是占的高度却不一样。有的是 100px,有的大于 100px。另外可以看出,垂直方向并没有居中对齐。
这篇文章主要研究:

  • font 的工作原理及度量参数
  • CSS box models 的类型、定义

字体度量

要弄明白上面问题的答案,需要先从字体说起:
我们拿出其中AvenirHelveticaHiragino Sans GB三种字体进行分析
字体占行高
由上图可知,在我们设置 font-size:100px 时,文字所占的高度分别为 137px115px100px
感觉有点懵啊。怎么 font-size:100px ,可是高度却由于字体不同,而不一样了呢?
在字体设计中一个字符所在的空间容器称为EM Square(也被称作“EM size”或者“UPM”)。

在传统的金属字模中,这个容器就是每个字符的实际金属块。每个字符的高度是统一的,这样每个字模可以整齐地放进行和块中(如下)。

传统的金属字模

字体的定义规则

  • 字母的高度被称为“em”,在数字化字体中 em 是空间的数字化定义总量。em的大小(以下均写为: EM size)通常是 1000 单位,在 TrueType 字体中,EM size 约定是2的幂,通常是1024或2048。
  • 根据其实际使用的单位,字体的度量可以根据一些设置来决定。注意,有些值是em-square之外的值。
  • 在浏览器中,相对单位是用于缩放用来适应所需的 font-size

字体的设置

字体的设置
这是一张详解字体设置的图例,图中各个属性的意义:

  • baseline (基线): 分隔 ascentdescent ,默认字符底端沿 baseline 排列,如图中的P,x,Ё(为俄文字符)
  • ascent (上升): 基线的上部分,字符最高处与 ascent 顶端可能有空白,由 font-family 决定
  • descent (下降): 基线的下部分,字符最低处与 descent 底端可能有空白,由 font-family 决定
  • xHeight (X 字高): 小写字符 x 的高度,由 font-family 决定
  • capHeight (顶面高度): 大些字符 P 的高度,由 font-family 决定
  • lineSpacing (行间距): 在浏览器中一般 lineSpacing = ascent + descent
  • lineHeight行高): 默认等于 lineSpacing,受 line-height 设置影响,如果设置 line-heightlineHeight 等于 line-height
  • half-leading (半行距): 如果lineHeight > lineSpacing,则lineHeightlineSpacing 之间会产生上下相等的空隙 (lineHeight - lineSpacing)/2 称为半行距half-leadinghalf lead strips)。

字符所占高度的计算

所以在了解了上面的概念以后,就可以解答为什么在 font-size:100px 的时候行高却不一样的问题。
首先,先下载一个专业的字体软件FontForge,这个软件运行在xquartz上,所以要两个都要装。

百度云通道

安装后我们以 Avenir 字体为例进行分析。
Avenir 字体详情截屏

  • EM size1000
  • ascent1000descent366
  • capHeight708
  • xHeight468

注:浏览器使用HHead Ascent/Descent值(Mac)和Win Ascent/Descent值(Windows),并且这些值可能不同。

这意味着 Avenir 字体在 1000 单位的 EM size 中使用了 1000 + 366 个单位,也就是说 font-size:100px,其高度为 100px * (1000 + 366 ) ≈ 137px
这个计算高度定义了 元素内容(Content area)高度 也就相当于 background 属性。

Avenir 字体解析图

CSS box models

接下来我们深入的研究一下,CSS box models。你可能不知道什么CSS box models,不过说出来你可能不信,在实际工作当中恐怕你最常见的就是CSS box models

Block Box/Containing Box (块盒子/包裹盒子)

比如有一段简单的文字,就有可能会有一些列的 box 。那么这个段落被称为 Containing Box ,之所以这么命名,可能是因为他包含了很多 box 吧。(呵…)当然你也可以称之为 Block Box,因为他就是一个。简单来说Containing BoxBlock Box其实是一个东西。
Block Box演示

Inline Box (内联盒子)

在段落内部,有很多的 Inline Box。 这些 Box 不会像 Block Box 那样形成新的一⾏。
Inline Box演示
在上面的例子中, <em/> 标签包裹的 斜体元素 就是一个典型的 Inline Box

Anonymous Inline Box (匿名内联盒子)

在段落内部,那些没有标记的Inline Box 则成为 Anonymous inline Box
Anonymous Inline Box演示

Line Box (行盒子)

所有Inline BoxContaining Box紧挨着排列,则会形成 Line Box。需要注意的是,Line Box是没办法直观看到的。
Line Box演示

Content area

Content area
Content area 是围绕⽂文本的隐形框。 而且在字体度量这一小节我们也证明过了,它的高度由font-size决定。

更详细的定义及说明可以访问 CSS 规范2.1 中关于 视觉格式化模型Visual formatting model)一节进行阅读。
中文站|W3C 官网

Inline BoxLine Box

Inline Box 如何影响 Line Box

Line box 的高度由 Line Box 中最⾼的 Inline Box(或Replaced Element)确定。
最⾼的 Inline Box 可以是⼀个 Anonymous Inline Box
line-box-with-anonymous-Inline-box

也可能是一个增加了line-heightInline Box。 由于增加了 line-height ,所以这个它会比其它的 box 更高。
line-box-with-increased-Inline-box

可能是⼀个更⼤的 font-sizeInline Box,这使得这个 Inline Box ⽐其他 Inline Box 更高。
line-box-with-increased-font-size

由于浏览器器的不同,它也可能受到上标或下标的影响。 因为有些浏览器以影响Line box的方式渲染上标元素。
line-box-with-superscript-inline-box

我们可以通过设置 <sup/><sub/>line-height 为 0 来解决这个问题。

1
sub, sup { line-height: 0; }

Inline Box 可能受到 Replaced Element(如:<img/><input/><svg/>) 的影响。

line-box-with-replaced-element

Inline Box 撑破 Line Box

正如我们所看到的, Line Box 将增加所有行内 Inline Box 的⾼度。
line-box-addtional-inline-box-height
但是,有时候 Inline Box 的一部分会撑破 Line Box 的顶部或底部。例如一个拥有paddingmarginborderInline Box 。由于 Inline Box 不能设定高度(设了也白设)。因此会在元素的上方和下方显示paddingmarginborder,但并不会影响 Line Box
注意:对于Replaced Elementinline-block行内元素paddingmarginborder都会增加高度,所以Line Box 的高度也会受到影响。
inline-box-poke-out-line-box
浏览器将按照文档的先后顺序呈现 Line Box。 所以, 后续行上的 border 可能会覆盖上⼀行的 border和文本。
pants-over-previous-line

参考文章
行内元素垂直方向的layout
深入了解CSS字体度量,行高和vertical-align
FontForge 与字体设计 - EM Square
Deep dive line-height