字体对齐真难啊

2026-07-26
7分钟阅读时长
Featured Image

很早之前,我们团队对接的设计同学推行了一个字体规范,凡是金额价格的数字表达,需要使用他们制作的某个自定义字体,该字体仅包含数字和英文等字符。

但该字体在后续的大量使用中发现在 iOS 上存在基于 baseline 无法对齐的问题,该问题在 Web 和 Native 场景都有同学反馈。

图片展示了设计稿与iOS实现的对比。左侧设计稿中,数字“991元”位于“通用优惠券包”下方,数字为红色,且有“券包”标识。右侧iOS实现中,数字“991元”同样位于“通用优惠券包”下方,数字颜色为红色,但“券包”标识位置有变化。图片直观呈现了设计稿与iOS实现中数字及标识位置的差异,与上下文提到的iOS上数字基于baseline无法对齐的问题相关,展示了问题的具体表现形式。

这个问题困扰大家久矣,目前大家的解法通常是 baseline 对齐之后,再将数字设置成绝对定位人肉再微调。整体解决方案比较 Hack。

图片展示了网页元素的代码及样式信息。代码中有一个span元素,其class为“box-number”,内有数字“991”。样式部分显示该元素字体为DouyinNumberABC,字体加粗,大小24px,行高28px,相对定位,top为1px,颜色为红色。该图片与文档中字体对齐难的问题相关,可能是为了解决数字字体对齐问题,通过代码样式调整来实现。

我们也曾尝试找设计同学,他们反馈这个数字字体他们已经不维护了,要解决的话只能新搞字体成本比较高。鉴于你们不清楚是否是字体产生的这个问题,且你们现在不是有解决方案吗,那就保持现状就好。

这个回复虽然让人有点无语,但确实比较现实。痛定思痛,我想着求人不如求己。虽然我不会字体设计不清楚原因,但我不会不代表 AI 不会呀!于是在 AI 老师的带领下,我的字体求知之路开始了。

怎么就对不齐了?

在此之前我们整体感觉就是这个数字字体偏上所以导致对不齐,且这个问题只在 iOS 中存在,Android 是没有问题的。所以我直接给 AI 下了指令,帮我分析字体的上下空白边距,的确发现上下边距是不一致的,下边距是比较高的。我让其帮我调整字体上下边距一致给我输出调整后的文件。最终 iOS 上使用该修改版确实能达到对齐的效果,但是在 Android 上整体就偏下了。

图片展示了有问题版和修复版的数字对齐情况。左侧有问题版,数字“3819”在“最高”下方偏上;右侧修复版,数字“3819”与“最高”对齐。右侧还展示了数字“3819”在不同背景下的对齐情况,数字与“最高”对齐。该图片与上下文紧密相关,用于直观呈现问题所在,辅助说明字体对齐不一致的问题及修复效果。

到这我就有点不知道怎么办了。直到有同学给了一张图,告知字形轮廓整体偏上,我顺势对比了下 Android 发现是正常的之后。于是我换了个思路,让 AI 分析下为什么 iOS 上数字会偏上,Android 是正常的。

这个时候 AI 开始发现真相了,他给了好几个关键字 OS/2 sTypo, hhea, usWin 等。告知我是因为这个字体的 hhea 和 OS/2 sTypo 配置不一致导致的。

图片展示的是文档中关于字体对齐问题分析及修复方案的内容。文档提到AI分析发现字体hhea和OS/2 sTypo配置不一致导致数字偏上,AI给出修复版文件。图片中详细说明了iOS和Android上数字对齐差异,iOS数字偏上,Android数字居中,还列出了hhea和sTypo的具体数值差异,以及如何选择不同度量导致平台差异。图片与上下文紧密相关,直观呈现了AI分析结果及修复方向。

OK 我暂时先不管这些东西是什么,先让 AI 按照它发现的问题给我一份修复版的文件看看效果。最终测试下来发现确实返回的字体偏上以及居中的问题都好一些,可以看到数字相对于左侧的标签的是居中对齐了。

图片展示了有问题版和修复版字体对齐效果的对比。左侧“有问题版”中,数字“3819”相对于左侧的“最高”标签偏上且不居中;右侧“修复版”中,数字“3819”相对于“最高”标签居中对齐。右侧“有问题版”和“修复版”也呈现了类似情况。该图片与上下文紧密相关,直观呈现了AI修复字体对齐问题前后的效果差异,验证了AI修复方案的有效性。

字体格式

在了解问题的原因之前,我们得先补习一些印刷和字体相关的知识。我们都知道活字印刷是通过预先对铅字块进行排版然后像盖章一样印刷。后续进入计算机时代之后,出现了照排技术,可以简单理解就是通过将一个一个字模在相纸上光照显影的形式替换了铅字块进行排版,最终将排版后的相纸进行印刷。再后来就是现在的数码印刷时代,所有的排版都是在电脑端完成,打印机通过感知排版后的效果逐行逐帧打印。

这里就会涉及到一个字怎么在电脑上存储表达的。我们现在大多数的字体都是轮廓字体。电脑就像是一支“笔”,字体文件中记录了这支笔从哪里开始起笔绘制整体字形的轮廓所有需要的数据。现代字体格式基本上都是这个逻辑,字体格式的本质不是艺术的存储,而是字体在电脑上可编程化的参数表达。

至于为什么有这么多字体格式,这就涉及到 Adobe, Apple 和 Microsoft 三家巨头公司的爱恨情仇了。

PostScript

PostScript 由 Adobe 在 1984 年推出,是桌面出版领域的革命性技术,使用贝塞尔曲线描述字形轮廓,能用少量数据勾勒出平滑复杂的曲线,支持无限缩放不失真,成为高端印刷和出版行业的标准。它本质是命令型编程语言,通过栈式虚拟机执行图形指令,为后续字体格式奠定了矢量轮廓的核心逻辑 。

早期 Apple 一直做的是硬件设备,乔布斯比较看好未来数字艺术这块的市场。所以当时和 Adobe 进行了合作,在 Apple 的打印机设备上支持了 PostScript 字体。

TrueType

但 PostScript 字体有个比较头疼的问题是太编程化了,你让字体设计师去直接写就很痛苦。所以最好是能有个软件来设计这个字体再转成 PostScript 字体文件比较好。所以当时 Apple 希望 Adobe 能把 PostScript 授权给自己,让他们在 Mac 上去做对应的字体软件。

这在现在看起来是个非常正常的思路,但是在当时闭源的世界大家都是守着自己的垄断软件故步自封。Adobe 担心授权给电脑之后被破解了他们的垄断地位就没有了。我们站在未来的视角看过去就会发现这个决策是非常可笑的。但 Adobe 确实做了这个决定,并在当时为他们的失策付出了惨痛代价。

Apple 在被拒之后痛定思痛,于是自己研发了 TrueType 字体规范,并和 Microsoft 合作进行推广。它的核心目的就是旨在打破 Adobe 在 PostScript 领域的垄断。TrueType 的最大优势是 Apple 吸取了 PostScript 的教训,它选择了公开 TrueType 技术规范。同时它集合了 Windows 和 Mac 未来两大电脑操作系统的支持,所以跨平台兼容性非常好。同时在两家公司的支持下,它保证了电脑屏幕显示和打印输出的一致性。

OpenType

虽然 Apple 公开了 TrueType 规范本身,但是基于 TrueType 还有很多衍生的技术把持在 Apple 手上。当 Microsoft 由于捆绑 TrueType 赚的盆满钵满的时候,流出羡慕泪水的 Apple 拒绝了 Microsoft 想要更多 TrueType 字体技术的授权的申请,期望通过复刻 Microsoft 通过在自家 Mac 上捆绑来实现类似的商业道路。

无独有偶,Microsoft 在被拒之后痛定思痛,转手就和 Adobe 合作开发了新的字体格式 OpenType。俗话说的好,商场如战场,敌人的敌人就是朋友嘛!

OpenType 字体整体沿用了 TrueType 的 sfnt容器,并对其进行了扩展。正因如此,可承载 PostScript CFF/CFF2 或 TrueType glyf 轮廓。在此基础上,OpenType 新增了连字、上下文替换、多语言支持、可变字体轴(2016年 OpenType 1.8加入支持)等高级排版特性,彻底扩展了字体的表现力,如今已成为跨平台字体的事实标准,支持从桌面到 Web 全场景的排版需求。

最终 Apple 在 Mac OS X 慢慢加入了对 OpenType 的系统支持,这场字体的商战慢慢才告一 段落。

字体度量

我知道你们很着急,但请先别太着急。知道字体度量之前,我们还需要知道一些字体的基础知识。

字体设计中,我们会定义一个 em 空间,作为字体的基础区域。基于 Baseline(也就是坐标原点)之上会定义一个上升空间 Ascender,之下会定义一个下降空间 Descender。对选定的一组字体纵向度量,推荐的 baseline-to-baseline distance 通常按以下方式计算:

行高(Line Heigt)= Ascender - Descender + LineGap

其中定义 Ascender, Descender 和 LineGap 的配置就叫 Vertical Metrics(纵向度量)。当然我这里列举的只是和本文相关的几个参数,纵向度量配置还有一些其他的配置项我就不一一列举了。

在 TrueType 格式中,使用 hhea 字段来描述相关的配置:

  • hhea.ascender
  • hhea.descender
  • hhea.lineGap

后续 Windows 在引入 TrueType 的支持的时候,额外增加了 usWin* 字段来描述最大裁剪区域,包含:

  • usWinAscent
  • usWinDescent

再后来到 OpenType 标准规范引入,为了保持兼容性,它并没有废弃 hhea 字段描述,而是额外新增了 sTypo* 字段描述度量配置。它包含:

  • sTypoAscender
  • sTypoDescender
  • sTypoLineGap

同时 OpenType 还增加了 USE_TYPO_METRICS 标志,当其值为 1 时优先会读取 sTypo* 度量配置。但不同的操作系统有不同的实现。前文咱们已经知道,Apple 创建了 TrueType 字体格式。作为 TrueType 的亲爹,咱们必须得高优支持 hhea 度量配置呀!

图片展示了FontForge软件中“DouyinNumberABC”字体的属性设置界面。界面左侧显示字体名称及“Medium”字体样式。右侧有多个参数设置区域,其中“自定义参数”部分,多个参数开关处于开启状态,如“typoAscender”“typoDescender”“typoLineGap”等,数值分别为930、-210、100等。该图片与上下文紧密相关,直观呈现了上下文中提到的字体度量配置参数在软件中的实际展示情况。

诚如 AI 所说,我们用字体软件打开字体配置一看,typoDescender 和 hheaDescender 的值明显不一样,且 hheaDescender 的绝对值明显要比 typoDescender 要大。这就解释了为什么 Android 和 iOS 上的表现为什么会不一样,且为什么 iOS 上体感会更靠上。因为 hheaDescender 代表的基线以下的下降高度,下降高度越大,居中对齐时字形就会看起来靠上。

我们使用 AI 将 hheaDescender 的配置改成和 typoDescender 的配置一致之后,最终双端的表现就拉齐一致了。

图片展示了有问题版和修复版字体对齐情况的对比。左侧有问题版中“最高3819元”字体对齐不一致,右侧修复版字体对齐一致。修复版中“最高”和“3819元”字体对齐统一,且“最高”字体颜色为红色,与“3819元”字体颜色一致。该图片与上下文内容相关,用于直观呈现字体对齐问题及修复效果,辅助说明字体对齐难的问题及修复情况。

怎么还对不齐?

我们修改 hhea 配置和 sTypo 一致之后发现,iOS 的表现确实好了不少,但是还是和右侧的元字有轻微的不对齐。这里就涉及到苹方字体中西文和中文字形的设计策略了。AI 告诉我苹方的几个字在本文测试的字体版本、字号和渲染设备上,截图中“元”的可见轮廓中心比数字低约 1px。所以即使按照 baseline 对齐,由于中文更靠下所以在视觉上仍然不会对齐。

图片展示的是通用优惠券包的价格信息。红色边框内显示“券包”二字,其右侧是红色数字“991”,下方为“元”字。下方文字为“通用优惠券包”。图片中红色边框与文字位置对应,与上下文提到的数字和中文字体对齐问题相关,可能是用于说明在不同系统下数字和中文对齐情况的示例,以辅助理解字形位移适配对字体对齐的影响。

图片展示了baseline对齐后的文字对齐情况。在实际字号下,数字字体为24px,苹方字体为12px。数字轮廓相对baseline的下边界数据给出,如1/2/4/7下边界为0px,3/5/6/8/0下边界为-0.288px。草方汉字相对同一条baseline的下边界数据也给出,如元、分下边界为-1.248px,角、张下边界为-1.272px。最后说明baseline对齐后,汉字可见底边会比数字低大约0.9 - 1.3px,与截图里的差值相符。

而 Android 使用的系统中文 fallback 通常不是苹方,而是设备对应的 Noto Sans CJK/厂商字体.字形轮廓、ideographic baseline、字体边距,以及系统的文本测量方式都可能不同。

所以这个问题已经不是修改字体整体配置能解决的了,它确实需要字形去做微调来适配。由于字形的位移适配是统一的,也会影响原本正常渲染的设备。为了保证影响最小化,AI 给我的建议是下移30个单元,实际移动距离不足 1px 在不同的系统上可能会由于渲染的取舍最终抹平差异。

这张图片展示了针对字体基线对齐问题的具体技术方案与参数说明,内容围绕修改字形轮廓调整字体位置的方法展开,核心说明了不同系统适配时的下移单位选择:提到在字体UPM为1000、24px字号下,不同下移单位对应的实际移动距离,如下移20单位约0.5px、下移30单位约0.72px;还给出了三个实验版本的方案,并明确了iOS下字形调整的建议,同时兼顾Android系统的适配,避免对原有渲染效果造成过多影响,为解决跨系统字体基线对齐问题提供了可落地的技术指引。

最终按照 AI 给的建议修改观察双端的效果,基本能比较符合预期了。

这张图片对比展示了字体对齐问题修复前后的效果,两组均包含有问题版与修复版的内容。左右两组中,“最高3819元”的字体内容,在有问题版里字形未对齐,而经过修正的修复版,字体的基线实现了对齐,对应文档中提到的为解决Android字体对齐问题,通过移动微调字形来适配,使双端渲染效果符合预期的内容,直观呈现了字体对齐问题修复的成果。

后记

后续为了不影响历史的场景,我将修改后的字体单独整理发布了个新的字体,后续碰到问题的场景再按需替换。现在我们再整体脱水总结下:

  1. 该字体的 hhea 垂直度量配置下降空间要比上升空间大,导致 iOS 中基于该配置渲染的字形垂直居中后依旧整体偏上;
  2. 历史原因,字体文件中存在多套度量配置。而 Android 等非 iOS 设备中在 USE_TYPO_METRICS=1 条件下会优先读取的是 sTypo 度量配置,所以 Android 上渲染和 iOS 有差异是正常的;
  3. iOS 中苹方字体设计上整体会偏基线靠下,导致基线对齐后视觉还是不对齐。最终通过微调字形下移实现对齐

相关文章:

Avatar
怡红公子 擅长前端和 Node.js 服务端方向。热爱开源时常在 Github 上活跃,也是博客爱好者,喜欢将所学内容总结成文章分享给他人。

0 评论