CSS 中英文排版大冒险

marisa-as-default-header

(credit: twitter 戀歌)

事出有因,我最近在设计软件(例如 Sketch)和页面的中英文排版上花了不少时间。

具体地说,我是想弄清楚在中英文混排时,为什么文字、图标等设计元素这么难对齐,这么多设计工具的行为与我们想象不同。

本文是对 CSS 相关排版的研究。Sketch 我已经写了一个插件去绕过 OS X 特有的排版问题。

阅前提醒:本人在排版方面的研究仅是浅尝辄止,欢迎高人点评错漏。

定义问题

请看这个测试例子,如果你不在 OS X 上(没有对应的系统字体),可对照以下参考图——

figure-01

每三个方块为一组,不难看出每组中间那个方块的排版和左右两个不同,不是高度无法对齐,就是文字无法获得同样的 Baseline(基线)。

但三个方块之间的区别,仅是 font-family 的优先次序不同;中间方块优先的是 Avenir Next,左右方块都优先选择冬青黑体(Hiragino Sans GB)。

相信做过前端开发的读者,都知道这是 font stack。

但当首选字体不存在特定字形(glyph)的时候,浏览器会怎么设置文字的高度?

如果这是中英文混排的问题,那么为什么仅有中文的元素也被英文字体的优先级影响了排版?

理解问题

我的第一反应是这些元素的 line-height 不同了(我在 stackoverflow 上也是这样问的),但诚如 CSS 和浏览器开发工具所示,所有方块的 heightline-height 都一样是 48px

也就是除了 font-sizeline-height 以外,还有影响排版的因素。

最好的暗示,可以通过选择文字得到;如图,被高亮的部分并不相同,为什么?

figure-02

figure-03

分拆问题

既然问题出在字体上,让我们先来看看影响排版的字体因素:Ascender(升部)和 Descender(降部)。

在 TrueType 和 OpenType 的定义中并没有排版学的基线,只有字形相对基线的最大距离。其中基线之上的最大距离为升部值,基线之下的最大距离为降部值(这里特指 hhea 表中的 ascent 和 descent 值;还有一组 OS/2 表中的 sTypoAscender 和 sTypoDescender 值。两组值的用途类似,但定义不同,也是跨平台字体排版的问题来源之一)。

或许你会问,东亚文字(CJK)没有基线的概念,怎么计算升部降部的值?答案是由字体设计师决定(其实英文也是由设计师决定,上述的只是规则,不是必须)。大多数字体(例如思源黑体)被定在 (0,-120) (880,1000) 的方框内。

但即便是相同的字体,相同的升部降部,不同的排版系统依旧会有自己的高度算法。CSS 2.1 标准给浏览器留了两个计算 content area 高度的选择:用最大升部降部的距离,或者用字体的 em-box

等等,content area 是啥?em-box 是啥?为什么不统一一套规则?

学习术语

一方面,CSS 的行内格式化模型(inline formatting model)远比 font-sizeline-height 要复杂。

另一方面,就我们的例子而言,font-sizeline-height 足以解释问题。

但首先,我们要搞清楚 em-box 和 content-area 是啥——

em-box = font-size = content-area

换而言之,在我们的简单例子里,content area 就是我们设的 font-size,它同时也等于字体对应的 em box。

但没人保证这个字体里的实际字符会比 em box 小或者大(解释了为什么同样是 font-size: 12px,字体不同,文字大小也相去甚远)。实际上,em box 也是由字体设计者决定的值。

至于为什么 CSS 2.1 选择不定义统一的计算规则。则是因为无论 em-box 还是最大升部降部的距离,都会导致一些不便的设计问题。多方相持不下,最后留待 CSS 3 解决。引用 Eric Meyer 在 CSS: The Definitive Guide 里的话,“在 CSS 2.1 范围下,是无法通过 CSS 真正完成精准的文字排版的”(第三版,194 页)。

说完了 font-size,还有这两个等式——

content-area + leading = inline box
inline box (min/max) = line-box

leading 的算法很简单,用 line-height - font-size 即可,浏览器会把 leading 均分至 content-area 上方和下方(解释了为什么设一个大于 font-size 的 line-height 会让文字垂直居中)。

但这个算法也意味着,在我们的简单例子里,inline box 的值就是 line-height 的值,同时也是 line-box(最终方形的高度)。

也就是除了 font-sizeline-height 以外,并没有其他因素影响排版。

等等,这和我们之间的推理相悖?

解答问题

让我们再看一次 CSS 2.1 的描述——

If more than one font is used (this could happen when glyphs are found in different fonts), the height of the content area is not defined by this specification. However, we suggest that the height is chosen such that the content area is just high enough for either (1) the em-boxes, or (2) the maximum ascenders and descenders, of all the fonts in the element.

简而言之,虽然行内包含多种字体的高度计算属于未定义,我们建议浏览器选择一个适合所有字体的 content area,一个符合所有字体 em-box 或升部降部最大距离的高度。

可以推测,浏览器们统统选择把排在前面的字体 em-box 也算进去。事实上,由于缺乏标准,根据 font stack 的不同,每个浏览器计算的值都不同——

figure-04

Chrome on OS X

figure-05

Firefox on OS X

figure-06

Safari on OS X

同时可以推测的,是我们选择文字时的高亮,反映的也是这个 content area。当然,浏览器们也是各有各的实现方式。

其他方案?

说到底,我们是遇到了一个无法依赖浏览器解决的未定义状况,那么只能我们自己想办法绕过它——

  1. 准备多套 font stack。对于使用 LESS 或 SASS 等 CSS pre-processor 的开发者而言,这是比较好的解决方向。如果你的网站是多语言的,有多个针对语言的 font stack 尤其重要。

  2. 将错就错。相信这是大多数开发者的解决办法,在已知 font stack 的情况下,我们可以估算 content area 的大概值,配上适合的 overflowpadding 作为调整。足够大的 line-height 也可以隐藏垂直不对等的情况。

  3. 想办法选择升部降部接近的字体。这不总是可行,但见上述的例子,流行的 Helvetica Neue 恰恰就有和冬青黑体更接近的值。如果你需要获取相关的 hhea 信息,使用这个 Font Inspector 工具,对于 OS X 的 TrueType Collection(ttc)文件,用 transfonter 解开成独立的 TrueType 字体。

  4. 欢迎建议 :)

后话

假如这个冒险故事告诉我什么道理,就是在前端开发遇到瓶颈的时候,就该去充电了。上述内容,其实在 CSS: The Definitive Guide(中文译作《CSS权威指南》)里都有详细谈及。只是没用到这一步,就无法体会其中的概念罢了。

完。

PS:本文使用中英混排留空格式撰写,仅为个人试验,并不代表作者支持或反对“排版鬼子”的观点(名称借用自 Grammar Nazi)。

PPS:本文 markdown 源在我的 Github 上。

Author: 店长

The Master of BitInn

2 thoughts on “CSS 中英文排版大冒险”

  1. 连桌面排版都没解决的问题,谈 CSS 和动态网页排版简直是天方夜谭。
    Ascender / Descender 建议采用原名;谭智恒给了一个逼格破表的「上椽」和「下椽」发在某本出名杂志上,太 Obscure 而我不太喜欢(

    有人建议 x-height 部分可以等同为中宫,我个人是不同意的。

Comments are closed.