排版技术札记(2):文本渲染技术(shaping篇)
首先需要声明,这是一篇类似综述的东西。之所以不涉及细节,是因为要把所有细节写出来,一百张A4纸可能才够(有大量的细节说明和示意图)。
文本渲染
通常提到的文本渲染技术,容易混淆,主要可以指两个,一个是shaping,另一个是rasterization。前者可以转换为等效的绘制指令,后者一般指图形学的栅格化。
文本为什么需要处理?
对于人们来说,书写是一种很简单的东西。汉字的书写,似乎没什么复杂的地方,但是世界上还有几种不一样的书写类型,比如有一种依赖上下文的,字母在不同位置会表现成不一样的形态,如阿拉伯文;比如有一种依赖上下文且会在字母四周加符号的,比如缅甸文。
这些文字,在纸上书写,过程似乎没有太大的区别。但是一旦转换场景,送入计算机,如何编码,就变成了需要约定的事情。
这种约定,一般先需要编码(encoding)。但是编码还不够,还需要约定能够表现书写文字的最小单位和最大单位,这个一般称为grapheme和grapheme cluster。
这两件事做了,事情还没完。grapheme和grapheme cluster都是以string形式存在的,简单说,就是一维的数组。但是文字呈现,却是二维的,从特定的string转换到带坐标属性的string,就是所谓shaping要做的东西。
不过,一般这种二维的带属性的string,会转换成特定字体的glyph的数组。这个glyph,本质上是图,是送给人眼看的东西,它可能是一个编码元素,也可能是几个编码元素,也有可能是一个编码元素的一部分。
文本处理的理论
计算机中的文本处理。熟悉形式语言学会有很大的帮助。如果不熟悉形式语言学,那么最少要掌握正则表达式一项技术。其次,如果对于关于书写的学科——书写系统——有所了解的话,对于理解文本处理会更加具体。
第二个需要掌握的是Unicode本身。一般来说,Unicode组织每年都会更新Unicode的内容。试图一劳永逸地解决所有问题,是不可能的。Unicode的东西,需要随时看,年年看,老的文档要看,新的文档也要看。
文本的呈现基础:字体
这里就切入核心的技术难点之一了。字体,文件本身,已经没有什么秘密了。一般要实现shaping库的,都需要从头解析字体获取shaping相关的数据。
但是,解析这事,本身可就有一些难点了。读的问题可能还没什么问题。写的问题可能就大一些。这两个都做过,对于二进制解析,就没有什么难度了。
文本的shaping过程
文本,使用regex分割文本,对分割后的文本进行调整,使用字体信息进行运算,获得二维glyph数据。
如何使用regex分割文本,需要看约定俗成的shaping model文档。这部分,在微软的OpenType相关文档中可以看到。如果需要看到历史版本,则需要去archive.org中查找。
林林总总的Shaping Model
第一类是不需要怎么处理的,通常称为standard scripts,比如拉丁文,希腊文,西里尔文,汉字。
第二类是需要处理位置变体的,比如阿拉伯文,希伯来文,叙利亚文。通常这类文字,也有bidirectional上的处理。
第三类是需要处理复杂附加符号的,比如印度的一组文字。印度的这一组文字,有两个shaping model,对应着不同的unicode版本,现在的版本是第二版。
第三类是需要处理mark异常情况的,比如泰文和老挝文。
第四类是一大类,微软称为universal shaping model。这个大模型处理的语言比较多,主要是受印度文字影响的一些文字,不过也有例外,它包含了古埃及的圣书文字。
第五类,基本上是单一语言特定处理,比如高棉文,缅甸文,谚文。
上面五类主要是OpenType的处理模型分类。此外,还有自带自动机的字体技术,Apple的AAT和SIL的Graphite,这相当于把上面的模型所描述的东西直接写入字体。
回到现实
现实中,这些文字处理的事情,基本上都有现成的解决方案。比如微软的uniscribe和directwrite,macOS的coretext,Linux和Android中使用的harfbuzz。
但是这些东西不一定都能够满足用户的使用需求。比如一个库需要热更新修正渲染行为的时候。这些东西在TeX里面其实是需要的。不过在TeX里面,有时候连glyph和metric都需要热更新。
自2015年,我开始构想自己的shaping库,已经过去八年。我觉得我现在的想法💡足够成熟了。可以开始搞了。