修复Twitter等网站在Chrome的字体显示问题

system-ui

Twitter的默认字体栈是

font-family: system-ui, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif;

这里别的都好说,问题就出在system-ui这个。

system-ui这个generic font的设计思路很美好:自动调用系统字体,给用户最原生的体验。这个思路用在移动平台上、甚至Mac OS都没有大问题,但是在Windows上问题就很多。

Windows简中默认的字体是微软雅黑,英文则是Segoe UI。当然,Segoe UI是没有CJK字符的,所以会fallback到其他字体。

微软雅黑最大的问题是中文字符还算不错,但是英文和假名都巨丑无比。所以,一旦调用了雅黑来显示日文、英文内容,立刻变得不堪入目。

对比:

雅黑
微软雅黑
Meiryo
Meiryo

所以,一旦system-ui导致调用了雅黑来显示非中文内容,效果立刻变得很差。

由于system-ui的这个问题,有许多主流网站已经刻意将system-ui从其字体栈中删除,例如GitHubBootstrap等等。不知道为什么Twitter目前还没有跟进(而且没记错的话,Twitter是换了新版界面之后才专门加上了system-ui)。这里有个CSSWG的相关讨论(发帖人是Chrome/Google团队的CJK字体专家Koji Ishii)。

Chrome对于system-ui的处理

不过既然标题专门提到了“Chrome”,就知道这事情没有这么简单。

确实,system-ui的behavior在Chrome和Firefox是不一致的。其核心区别在于,如何处理lang参数。

很久以前在S1发过一贴专门讲不同浏览器不同语言的fallback机制(待更新),不过这里不展开。具体到这个问题,就是Firefox面对system-ui,仍然会根据语言来选择在选项里设置的字体,而Chrome会原封不动的直接套用系统默认字体——对于简体中文Win来说,就是微软雅黑。

回到Twitter上的话,Twitter其实有个很优秀的功能就是对于每条tweet会自动分析语言,然后给那个div套上lang='xx'的选项。所以,大部分日文推是已经加了lang=ja了的(如果没加这个问题会变得更复杂)。对于Firefox,面对system-ui,看到lang=ja他会先选择你设置的日文默认无衬线字体——没记错的话默认是Meiryo——显示效果就比较美观。而Chrome里则会无脑显示为微软雅黑。

用这个HTML可以很快速地对比两者。

可以看到,对于指定了“Segoe UI”的场合,由于Segoe UI无法显示CJK字符,会fallback到其他字体。这里,无论是Firefox还是Chrome都会根据lang来选择合适的fallback字体(ja=Meiryo、zh-cn=雅黑。en的话,由于我浏览器语言优先级是zh>ja,也是雅黑)。

对于指定了system-ui的场合,Firefox的逻辑是和其他西文字体完全一样的,对于CJK字符会根据语言选择fallback字体。唯一的区别在于这里对于lang=en的场合他是选用了有衬线字体TNR,因为Firefox字体设置里对于Latin的默认比例字体是衬线而不是无衬线。不过一般网站的CSS都会在字体最后指定sans-serif,所以实践中不会见到。

补充:我后来听说,Firefox似乎其实根本就不支持system-ui这个关键字。所以其效果等价于根本不加这个关键词。但是上面的分析的结论也没错就是了。

而Chrome那边如上文所述,会直接选用雅黑。由于雅黑字符集全,自然所有字符都雅黑了。假名巨丑,中日不同形的字符也会变成中文字形(这里的“将”)。另外,Chrome对于lang=en汉字会fallback到宋体而不是微软雅黑也是个多年以来的quirk了,我倒是不是特别不喜欢宋体所以不是太有所谓。

另外,强调一遍以上的都是在简中系统测试的。如果你的系统是英文,那默认字体则是Segoe UI,所以是从小(字符集)往大(字符集)fallback,不会受到雅黑的污染。

Workaround

在Chrome 78之前,我是使用了一个全局的Stylus rule来对付system-ui的:

@font-face {
    font-family: system-ui;
    src: local("Segoe UI");
}

但是在Chrome 79之后,你无法再使用@font-face来重新定义保留关键字了,说是这样才符合标准(那个标准我读了几遍都读不出这层意味,专门去CSSWG问了下,不过那边的专家都说确实不应该能覆盖。另外,Firefox从来就不支持用@font-face覆盖generic keyword。)

既然现在不行了,那只好手动替换Twitter了:

@-moz-document domain("twitter.com") {
* {
font-family: "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif !important;
}
}

顺便一提,我还替换了几个在Windows字体渲染效果极差的字体:

@font-face {
font-family: "M PLUS 1p";
src: local("Meiryo");
}
@font-face {
font-family: "mplus-1p-regular";
src: local("Meiryo");
}
@font-face {
font-family: "M PLUS 1p";
font-weight: bold;
src: local("Meiryo Bold");
}

浏览器对full range视频的支持

为什么会去折腾full range video这个小众的东西?这次还真不是我闲的蛋疼,事实是我发现很多Twitch直播已经开始用full range了(例子有现在火热进行中的Supermajor(PGL)和之前的Epicenter),然后用Firefox看就发现颜色不对,对比度爆表。

之前有“控诉”过Firefox对视频的支持程度落后Chrome一个世纪,当时提出了几个Chorme支持、Firefox不支持的东西:

  • RGB视频
  • 4:4:4或者4:4:2视频
  • Full range视频
  • 对nV显卡的支持不好

当然,这几条还是对的,但是我今天发现Chrome对full range的支持也不是完美,所以还是客观起见,详细说明一下。

说到这个话题,不得不先重复一遍我之前在各种场合提过多次的所谓“对nV显卡的支持不好”具体啥意思。其实,就是在Win 7 + nVidia显卡的默认设置下,Firefox播放视频的色域(color range)会错误。这里的“错误”,严谨地说是limited range(就是99%的视频)不会正确拉伸到full range。

这个问题的产生原因是Win7不支持D3D11 DXVA,而Firefox没有对D3D9 DXVA进行优化。其实,之前Chrome也有这个bug,但是在我提出之后Chrome几个月后就修复了。这个问题的解决方案,除了换A/I GPU或者升级到Win 10这种废话,就是

  1. 软解视频,即把media.hardware-video-decoding.enabled设为false
  2. 修改nVidia控制面板的一个设置,从“通过视频播放器设置”改成手动设为full range:

QQ图片20180609164804

我之前一直是用2,倒是不会影响本地播放器的播放(本地播放器我本来就没开DXVA)。

现在说回full range视频的解码。先说Chrome那边:软解的情况下(设置chrome://flags/#disable-accelerated-video-decode 为Disabled),完美无缺。硬解就不太行了,会把视频强行进行一次伸张,导致颜色被clip。我发的bug ticket在此,这里有个视频可以测试:

当然,你也可以去上述的控制面板手动改成“limited”,来让他不伸张,所以正好得到正确的颜色,但是很显然这样又会毁掉所有的limited range的视频的颜色,所以还是老老实实软解吧。

Firefox除了硬解和Chrome有一样的问题以外,软解也不行——我发的ticket在此

基本内容就是这些了,顺便讲下上面那个视频是怎么造出来的。首先自然是ps里把那个图画出来,然后用FFMPEG做。上次已经提到了记得要加-vf scale=out_color_matrix=bt709来保证输出的YUV是BT.709的,那么full range怎么处理?我网上搜了下意外地发现相关信息相当少,一开始搜到一个什么-color_range 2(这个参数ffmpeg的documentation根本没提到啊喂),后来发现效果其实就是强行在元数据里塞个full range,结果视频的像素数值还都是16-235范围内的,出来就是个灰暗的视频囧。

最后在这贴搜到原来要用-pix_fmt yuvj420p才能输出0-255的视频。-color_range 2都不用加,出来的视频自动metatag都是full range了。我用的完整命令行如下:

ffmpeg -loop 1 -i colortest_hd.bmp -vf scale=out_color_matrix=bt709 -color_primaries 1 -color_trc 1 -colorspace 1 -t 30 -pix_fmt yuvj420p out_420_709_full.mp4

Chrome 字体偏淡的问题

开始这话提前得先稍微讲一下Windows字体渲染机制。

在Win Vista-7这段时间,Windows的核心渲染机制是ClearType这个RGB次像素级渲染机制。所谓次像素,就是通过exploit屏幕成像原理(同一个像素的RGB三种颜色并不是一个发光元素,而是并列的三个)来增强锐度。叫做次像素,是因为你实质上用了某个像素点A(比如某个字母中的一个像素)边上的另一个像素B的一部分(比如只用其中的R元素)来增强了像素A。当然这本质上还是俩整像素,只不过A还是纯黑,B是一个红点;但是从人眼看来,就只是增强了对比度的A(这只是个例子,具体选什么颜色,是要从感知学上出发的)。次像素级渲染除了增强对比度之外,还可以增加所谓的appreant分辨率,也是类似原理。更详细的文章可以参见维基百科

具体说来,又能分成RGB类型和灰度类型,RGB就是为了增强某种颜色,甚至可以产生其他颜色,比如这个NN放大600倍的图:

untitled-1

可以看到字虽然应该是纯黑的,但是添加了很多RGB级的杂色,缩小了看锐度就会很高,而且笔画之间也相对平滑(比起点阵字体)。

而灰度级就是负责增加锐度和平滑度的“次像素”只用灰度了,很好理解,就不上截图了。(其实我个人感觉纯灰度的平滑/锐化机制不能算“次像素”…毕竟并没有利用到“次像素”的单元。不过算是约定俗成的叫法,就姑且这么叫了。)

Windows的ClearType一直是在RGB次像素渲染这个阵营的,与之相对,苹果的OS X一直是用灰度级。RGB次像素渲染的优点是锐度可以达到更高,但是会有不自然、平滑度不够的一些问题。其实就是一个取舍。

但是,RGB次像素渲染有个特点:和像素点的排列方式有关。如果像素的排列方式变了,锐化出来的效果就非常惨。因此,在Windows控制面板里,你其实是可以设置像素的排列顺序的。那么这就牵扯到一个问题:对于方向固定的显示器还好,对于经常需要旋转的手持设备,这是一个巨大的缺点:你总不能每次用户旋转屏幕之后,就重新刷新一下渲染设置吧?这也是为什么无论是iOS还是Android,都只有灰度的次像素级渲染。

screenshot_20170122-063529
Android 7.1 的字体渲染,放大500%倍

Windows从8开始,开始走手提设备和Desktop一体化,尤其是推出了许多官方的第三方的Tablet产品。为了应对上述的旋转问题,微软又不声不响地启用了另外一套字体渲染机制——没错,就是只有灰度级的次像素渲染。至于这个机制是否还叫ClearType,我也说不清楚,不过这个不重要。下面为了区分,我们就把带RGB的叫ClearType,不带RGB的叫做Win8+新机制。

在Win 8起的Windows中,只有部分桌面应用才会默认启用ClearType;而系统UI、Metro应用、甚至包括一些微软家的桌面应用(例如IE、Edge、Office从2013起)都完全弃用了ClearType而转用Win8+新机制。所以,在研究ClearType时千万要小心,不要误用了这些程序做范例,那是完全不同的机制。

不过,大多数第三方应用,包括Chrome和Firefox,仍然使用ClearType。所以,这个不管是Win 7 还是Win 10都一样。但是,同样是ClearType,Chrome的比起Firefox一直有偏淡的问题,相信眼尖的朋友早就发现了。

这个问题不知为何在Win 8/10下的宋体上变得更为明显(不知道是渲染机制的调整所致还是微软修改了宋体,我倾向于后者),几乎已经处于无法接受的范围。(手头没Win 10,回头补个图。)

之前在bug tracker各种场合提过多次一直未能得到关注,于是后来我自己file了一个:issue 534732。可算有了一定的关注度。

不过一直没人修或者回应,直到前几个月,才有人提出:Chrome没错(和Windows字体查看器里的效果一致),错的是Firefox。我看了下他的示意图,也自己试了下,倒也没说错;确实是Chrome的和Windows字条查看器更接近。不过,这(在我个人看来)不能反驳Firefox的效果更好的事实。

直到今天,终于事情有了进展。 lwchkg@chromium.org细心地发现,其实Firefox偷偷调整了一个ClearType的参数——enhanced contrast。从默认的0.5调整到了1。最搞笑的是,在Firefox设置相关的代码的注释中明明默认值还说是50(或0.5),但是实际实现代码中默认值却是1。

所以事情其实就是这么简单——Firefox和Chrome两者有不同的对比度增强参数而已。这位用户也同意1的效果更好。考虑到他是chromium计划的开发者,希望在不久的将来,Chrome也能做出更改吧!

 

 

几个浏览器坑爹案例小记

上次说好的什么问题都要document一下的,这次简述一下这两天被坑的经历。

Chrome

微软系的网站的自动login功能(也就是通过cookie)一直都做得稀烂。例如,如果你上Surface的网站,看右上角。有时候你会发现,虽然你明明已经是登录状态,但是那里会显示“sign in”而不是你的头像。如果点开,会发现在drop down menu里有你的账号……但是点了可能要重新输入一遍密码。这个只是稍有不便罢了,另外一个例子,也是我碰到的,就是这个微软App商店的例子。因为我的cookie记录里已经有关于微软的login信息了,所以当我试图打开这个网站的时候,他会用302重定向的方式先重定向到一个login“中间人”页面(域名是login.live.com的)然后再重新跳转回原界面。姑且不说这个用户体验是多么的糟糕,我遇到的问题是,那个跳转页面根本有问题:跳转到login.live.com之后,他似乎没能正确地读取我的登录信息,会再次跳转到下一个login.live.com的页面……如此迭代了4、5次之后,他终于放弃了治疗,直接给我返回一个纯白的页面(错误信息404)。

如果你在网上随便搜live.com blank page之类的,可以搜到无数的结果(虽然没有一个回复靠谱的,各种官腔)。不过,介于我在隐身模式以及别的浏览器没有这个问题,我想来肯定是清理一下cookie就OK了。可是很不幸地,我手动清理了所有和微软相关域名下的cookie和其他本地信息后(通过Chrome自带的选项:chrome://settings/cookies),依然会遇到这个跳转问题。我怕是可能我漏了什么微软系的域名没清理,于是进而用该工具清理了所有本地信息,结果居然依然可以重现这个问题!只不过状况略有不同:打开上面那个app的网页会直接问你登录(注意:这里就很奇怪了,所以为什么看一个App的介绍网页需要强制登录啊?如果开个新profile或者隐身模式,明明是不需要的的,可以游客浏览),登录之后又重复上面的无限跳转循环。

最后,我发现只有彻底删除

AppData\Local\Google\Chrome\User Data\Default\Cookies

这个文件才能解决。当然,代价就是我会丢失所有的cookie了。另外一个吊诡之处在于,我之前用Chrome内建的清理功能删掉所有的cookie的时候,这个文件的大小居然是不变的(2M),不过这可能是单纯地数据库没有vacuum而已。

Firefox

Firefox这次遇到的问题其实说来比较简单,就是我闲着蛋疼用Nighly跑了一下我的主Profile之后,整个local storage的文件(webappsstore.sqlite)就嗝屁了,似乎进入了只读状态。导致大量奇怪的症状,诸如脚本数据丢失、甚至无法登录Firefox Sync等等。这问题应该仅仅是个例(比如说我非正常退出了之类的),之前我经常交替用不同版本的Firefox跑同一个profile,从没出过这类问题。我只好再次删掉了整个文件——之所以说再次,是因为我上次因为Firefox的性能问题已经删掉过一次了。

(翻了下前文发现这事儿没在blog里提过,这里顺便说一下。Firefox的webappsstore.sqlite的大小会严重影响Firefox的响应速度,尤其是诸如YouTube这种本身非常消耗资源、又大量使用local storage的网站。一般人的这个文件可能只有几百K到几M而已,我那用了快10年的老profile的这个文件居然有40M之巨。删掉之后,在YouTube的流畅度立刻上升一个等级。)

当然也不是没有代价的,很多网站、甚至插件、脚本的本地信息都存在这里面。不过一般而言还好这些信息都不是特别重要,只是不方便罢了。如果真的有非常在意的数据,可以先用sqlite browser之类的软件把信息提取出来备份一下。

相比之下,Chrome的架构就要好很多了,在 AppData\Local\Google\Chrome\User Data\Default\Local Storage 下每个网站的数据是分成单独的文件保存的,想删哪个点哪个。Firefox最近也正在进行对该文件结构的改造,在不久的将来应该也会分成多个文件来提升性能(具体怎么分,会不会也按照网站分就不清楚了)。

Firefox flash

得顺便黑一下Firefox上的Flash性能了。众所周知Firefox上的Flash性能一直不怎么样,但是我以前用台式机看个视频还是很流畅的。结果最近开始,看个Twitch上的区区720P 60 fps的视频居然就疯狂跳帧,有肉眼可见的不流畅。如果用我的5年前买的笔记本来看的话(还是核显,因为笔记本win10双显卡驱动不支持浏览器开启独立GPU,FML),性能就更可怕了:整个的帧率居然只有20fps,相比之下Chrome至少能跑到40fps(在表扬Chrome的同时,可以看到Flash这东西真的无法拯救了……)。

主要的因素根据Reddit网友的说法,Chrome(据说现在IE也是了)是用的自行开发的改版Flash player,而Firefox用的是官方提供的插件版,所以性能要差劲一些。另外一个例子是Opera,虽然内核和Chrome一样,但是因为没有用pepper flash(Chrome改版)而用了Adobe官方版,性能也很差。当然,Mozilla也在开发自己的Flash player——Shumway,但是Mozilla的效率你懂的,和Valve Time™差不多了。

对于Twtich,还好现在有个脚本可以调用HTML5播放器,用了它就连我的笔记本都能流畅播放60fps的直播了。当然这个脚本并不是很完善,有时候会有音画不同步或者视频加载不出来的问题,一般刷新就能解决。实在不行,还有Live Streamer的选择——但是我比较喜欢twitch网页的界面(尤其是看聊天)。台式机的话我还是继续用Chrome+Flash看了,反正性能绰绰有余。

顺便一提,Twitch现在的control(就是播放、音量按钮)已经是用HTML绘制的了,但是在Firefox上有个绘制的bug,会导致全屏播放模式下性能极差(高CPU占用,低FPS)。我记得有个样式表workaround但是暂时找不到地址了。