木匣子

Web/Game/Programming/Life etc.

修复跨平台 Webfonts 渲染不一致的问题

Issue

最近开发的几个 Web 项目都遇到了同样的问题,在 Windows 上与设计稿 pixel-perfect 的网页,在 macOS 或 iPhone 上测试的时候却出现字体偏移现象,如下图所示:

font-rendereing-os-different-os

与这个 span 相关的的 css 大致如下:

.gs-header-menu__mega-toggle span {
    font-family: Larsseit;
    font-weight: 700;
    font-size: 1.6rem; // 16px in this case
    line-height: 1.33;
}

仔细观察发现 span 框的大小是完全一样的,其高度为 \(16 * 1.33 \approx 21\)。但里面的字体图元却渲染得不一样,即使把 line-height 设置成 1,也无法解决这一问题,在 macOS 中渲染出来的字体仍然偏上。这一问题对 CSS 来说已经超纲了。虽然我不是特别强迫症,但是这样的问题却非常吸引我,非解决不可。

于是我做了一些研究,找到了一篇非常硬核的文章:Deep dive CSS: font metrics, line-height and vertical-align,文中作者介绍了字体是如何被渲染到网页上,以及字体文件中的各种参数是如何影响渲染。

文章中提到了一个跨平台的开源软件:Font Forge。我们可以用它来直接编辑字体文件,让我们有机会深入字体文件,一探究竟。

Solution

首先我们要找到对应的字体文件。字体是设计师指定的,由客户在字体商店购买授权后发给我们的。为了方便在项目中使用,我将其 @font-face 重新编排,只要根据 font-family, font-weightfont-style 就能找到对应的字体文件:

@font-face {
    font-family: 'Larsseit';
    font-weight: 700;
    font-style: normal;
    src: url('#{$fonts-directory-path}/39A063_4_0.eot');
    src: url('#{$fonts-directory-path}/39A063_4_0.eot?#iefix') format('embedded-opentype'),
    url('#{$fonts-directory-path}/39A063_4_0.woff2') format('woff2'),
    url('#{$fonts-directory-path}/39A063_4_0.woff') format('woff'),
    url('#{$fonts-directory-path}/39A063_4_0.ttf') format('truetype'),
    url('#{$fonts-directory-path}/39A063_4_0.svg#wf') format('svg');    
}

商业字体通常提供了多种不同的格式,在兼容不同浏览器的同时作了一些优化。例如 woff2 可以在最新的浏览器上使用,文件小,加载快。其它格式可以参考这个页面:不台平台的 Webfonts 格式支持情况

客户提供的字体文件多达 60 个(1款字体 * 12个字型 * 5种格式)。由于字体的问题只出现在 macOS 和 iOS 上,所以我们只需要处理 woff2 格式的 12 个字形即可。

针对上面这个字体 39A063_4_0.woff2 我们用 Font Forge 编辑它。在 Element > Font Info... > General 面板中可以看到如下参数:

General Font Info

其中 Em Size: 1000 表示其字体的设计尺寸为 1000 单位。Ascent: 766 是一个辅助线高度,表示在基线上 766 单位,设计字体时顶部不要超过它,(例如带注音的字母ÉÅÑ)。Descent: 234 是另一个辅助线高度,表示在基线下 234 单位,设计设计时底部不要超过它,(例如有尾部的字母gjpqy)。如果对 em 这个单位有兴趣的话,可以阅读一下这篇文章

以上三个参数是供字体设计师在设计的时候使用,并不影响渲染,甚至在设计字体的时候,对某些非常张扬的字母,还可以不遵守这些辅助线。所以设计师在设计完字体的时候,还要另外设置另外的在渲染时使用的参数。这些参数在另一个页面:OS/2 > Metrics

OS/2 Metrics

对于如何设置这些个参数,业界也没有一个统一的说法。比较主流的实践有这两个,一个来自 Glyphs App,另一个来自 Google Fonts。我作为 Web 前端,自然是站队后者。

以 Google Fonts 的规范为例,需要调整参数如下:

  • Win Ascent/Win Descent:将其设置为字形中的最高和高低线,防止 Windows 裁剪字型。
  • Really use Typo metrics:启用该选项,引导 Windows 使用 Typo * 来控制字体的基线和行高。
  • Typo Ascent/Typo Descent:设置 Ascent 为 Cap Height 与 Ascender Height 中较大的一个。设置 Descent 为 Ascent - EmSize。
  • HHead Ascent/HHead Descent:保持与 Typo * 一致,确保 macOS 与 Windows 有相同的渲染效果。

依照这个规范,对比我们现在使用的这个字体。之所以在 Windows 和 macOS 上有不同的渲染效果是因为没有启用 Really use Typo metrics,导致 Windows 上使用的是 Win * 的基线,而 macOS 使用的是 HHead * 的基线。所以只要将 Really use Typo metrics 钩上,保存并导出字体即可。如果原本字体中的 Ascent/Descent 设置不符合要求,可以自行按上面的标准进行微调。

Example

以下是使用这种方法修复 URWDIN 字体的示例:

Windows

URDIN-windows
在 Windows 上修复前字体使用的是 Win * 基线,整体偏下。line-height: 1 时溢出 span 框;
修复后使用 Typo * 基线,与 macOS 表现一致。

macOS

URDIN-macos
在 macOS 上修复前后使用的都是 HHea * 基线,没有区别。
与修复后的 Windows 示例一致。

但是如果要将带注音的字母ÉÅÑ也包括进去,则 Typo * 以及 HHea * 的 Ascent 就需要再往上调整了。(不过我们这个项目无须考虑这个问题。)


在研究的过程中,我还发现有一些在线工具可以帮你修复类似的问题,例如 Font Squirrel Generator。但是这些工具会将商业字体加入黑名单来避免不必要的版权纠纷。如果使用的是开源的字体,没有版权限制的话,到是可以试试。通常开源字体有社区维护,被广大用户使用。经过反馈和修改,几轮迭代后质量反而更好,例如思源系列

木匣子 2019 on S3

一个多月前休陪产假期间,想着好久没写博客了,但 AWS 上还开着个 EC2 ,挺费钱的。不如把博客静态化放到 S3 上面吧!?经过短暂的选型,最终决定使用 hexo 这款基于 node.js 的静态博客系统。原因当然是出自于对 javascript 的喜爱。

于是花了一两周的时间,把十年来使用的三个不同的博客系统(分别是 wordpress/typecho/ghost)导出后整合到了一起。整合的过程虽然借助了一些 exporter/importer ,但是不同平台间还是存在很多差异,最后通过手工编辑修复了许久。好在文章少,时间多。

没想到在试用了一段时间的 hexo 后觉得静态化是如此的美妙:可以直接用文件系统管理 markdown 格式的日志文件,还可以直接用 git 做版本控制。生成静态文件和部署也很容易。

于是又花了几周的业余时间学习 hexo 模板开发,并写了现在使用的这套博客模板(hexo-theme-mutoo),虽然风格朴素(大概是因为设计细胞「用进废退」了吧),但是在一些小细节上还是花了点功夫。

内容和模板都完成后,就到了部署环节了。由于所有内容都是静态文件,得益于 S3 的 static website hosting 功能,可以直接将生成的静态网站整个同步到 S3 的 Bucket 中。然后就可以通过 S3 提供的地址访问网站了。

http://<bucket-name>.s3-website.<AWS-region>.amazonaws.com

同步的工作也很容易,配置好 AWS Cli 后,直接:

$ aws s3 sync ./public s://your-bucket-name --profile s3-user

其中 --profile s3-user 是在 IAM 中预先设置好的用户,并绑定了相应的 Bucket 读写权限。该命令如此简单,以至于不需要为 hexo 专门设置一个 hexo-delopyer-s3 。只要把这一行放到 package.json 的 “scripts” 字段下就行了:

{
  "scripts": {
    "clean": "hexo clean",
    "server": "hexo server",
    "generate": "hexo generate",
    "deploy": "aws s3 sync ./public s3://your-bucket-name --profile s3-user"
  }
}

然后可以通过 $ yarn deploy 进行部署。

S3 默认提供的域名又丑又长,可以考虑绑定独立域名,但是有个缺点就是 S3 服务不支持 https 协议。所以建议使用 Cloudfront 这个 CDN 服务作为反向代理来访问 S3 中存放的页面,同时提供 https 协议支持。

除此之外,Cloudfront 还支持绑定 Lamda@Edge[1] 来实现一些请求的重定向,弥补了静态网站的不足。毕竟重定向对于一个正在迁移重组的网站是非常重要的,可以避免站外的一些老旧链接失效。

静态化博客免去了数据库维护的负担,Cloudfront 免去了证书管理的负担,一身轻松呀!至于价格,对于一个小博客来说,S3 + Cloudfront 真的是超级的便宜。

突然间又重新燃起了写博客的欲望。

Postscript

当时在比较静态博客生成器的时候,把目光局限在 “written in javascript” 上了。最近发现一款用 go 语言编写的静态网站生成器 Hugo 比起 hexo 更快更强大,有很多 hexo 没有的功能。有空来研究一下,看看未来有没有可能迁移到这个平台上,或者用它来制作其它项目。


  1. 一种专为 Cloudfront 服务的 Lamda 分布式脚本

如何劫持网页上的 Vue 实例

最近老婆在家休产假,偶尔会在网上看看电视剧。但是我们常去的那个网站除了烦人的微信二维码观影限制,还加了片头广告等。二维码观影限制的实现在「使用 WebpackJsonp 与 jQuery 进行代码注入」一文中提到过:强制在影片播放前显示一个微信公众号的二维码以及一个观影码,然后向后台不停轮询公众号平台是否收到观影码来决定何时开始播放影片。

前文中我介绍了如何借助早期版本的 Webpack 以及 jQuery 来实现前端劫持。不过前端是一个快速发展的行业,这些方法很快就过时了,所以要有新的思路来实现原来的需求。

站方升级时引入了国产的 umi 框架,数据和模块都是在初始页面加载后动态载入的。而且这次他们不再将 jQuery 暴露到全局,所以之前的方法不管用了。此外他们也移除了 production 中的 sourcemaps,给阅读源码增加了难度。

不过幸运的是他们的播放器组件仍然使用 Vue 框架编写。使用 Vue 意味着组件的接口完全普被暴露在全局中,可以通过 DOM 的 __vue__ 变量访问到 DOM 绑定的 Vue 实例,以及实例中的所有成员变量和方法。

使用 Github Pages 托管主页

「所码即所得」一文中我创建了一个小项目。该项目构建后会生成一个静态页面:

  • 一个网页:index.html
  • 一个样式表:bundle.css
  • 和一个脚本:bundle.js

原先我将他们与博客一起托管在自己的 EC2 上面,但是由于现在我将整个博客都用 hexo 引擎静态化并放到 s3 上,不必再支付昂贵的 EC2 实例费用了。于是我打算将首页也搬走。

搬到哪去呢?我可以将主页也放到 s3 上。或者直接使用 Github Pages 服务。因为正好这个项目在 Github 上是开源的,所以可以免费使用 Github Pages 服务来托管项目页面。

所码即所得主页

0x00 Self-printing Homepage

很久很久以前注册 mutoo.im 域名的时候,给自己弄了一个很简陋的主页。如果用 Wayback Machine 往前翻,可以找到这一记录(找到的最早的快照是 2013 年初的):

当时的想法是:简单、另类,最好可以用代码来表达想法。但是因为刚弄的网站没什么东西可以表达的,就直接丢了个链接。这一丢就丢了将近五六年。前两个月再看到它的时候,觉得是时候重新装修一翻了。于是有了这个项目:Self-printing Homepage

Self-printing Homepage

使用 WebpackJsonp 与 jQuery 进行代码注入

本文本涉及的版本为 Webpack 2.x-3.x,写作时 Webpack 已经发布 4.x 半年左右。

0x00 Webpack

Webpack 是当下最流行的 Web 打包工具,允许前端开者发在项目中进行模块化开发,并将代码打包成一个代码包(bundle),在浏览器上加载使用。但随着引入的第三库越来越多,代码包会越来越大,影响页面加载时间。于是 Webpack 提供了一个分包功能,可以将共享的库拆分(code-spliting)到不同的包中,使得浏览器可以并行加载不同的包,加快页面打开的速度。

MAMP 与 PhpStorm 远程调试

最近接手了一个用 PHP 作后端项目。本来团队里一直都是使用 Vagrant 来统一开发环境的,但是这个项目可能比较老,所以没有整合虚拟机。于是需要手动配置开发环境。配置中途遇到了一点问题,需要断点调试程序,方便追踪错误。本文相关运行环境:

Windows 10
MAMP for windows 3.3.1
Apache 2.2.31
PHP 5.3.23

黑客帝国字幕流效果

翻收藏夹的时候发现压箱底的 yupoo 图床居然还活着,里面放了一些以前折腾过的东西。其中有一个动图是 08 年的时候用 Flash 做的黑客帝国字幕流特效:

matrix rain

第一感觉是:哇塞我居然做过这个!当时高中的时候用的是 Nokia 3110c ,内置塞班 S40 系统。这玩意儿居然支持 Flash 屏保。于是就写了上面这个程序,可惜性能太差,在手机上运行非常卡,最后导出成了屏幕录像凑合着用了。

现在又回来做前端了,想试着在网页上重新实现这个特效。但这么多年过去了,原来的源文件也不知道哪里去了,只好从零开始。