搞跨端渲染?你绕不开的HarfBuzz原理
本文是HarfBuzz系列的第二篇:

本文概述

一、关键概念与结构
1.1 script
HarfBuzz 中 script 指的是文字系统的类型,注意不是指语言,不同语言也可能属于同一类书写系统,比如:
| hb_script | 举例 |
|---|---|
| HB_SCRIPT_LATIN(拉丁文) | 英语、法语、德语、越南语等 |
| HB_SCRIPT_ARABIC(阿拉伯文) | 阿拉伯语、波斯语(Farsi)、乌尔都语等 |
| HB_SCRIPT_HAN(汉字) | 中文、日语汉字(Kanji)、韩文汉字(Hanja) |
| HB_SCRIPT_DEVANAGARI(天城文) | 印地语、梵语、尼泊尔语等 |
| HB_SCRIPT_CYRILLIC(西里尔文) | 俄语、保加利亚语等 |
| ... | ... |
我们在从0到1自定义文字排版引擎:原理篇中有提到:
Unicode 为每个 code point 定义了一个 script 属性,文本分段时就是按 script 属性连续来划分的。
比如对于Hello世界あい,从左往右扫描字符串,每遇到 Script 改变,就切分出一个 run,最后会被划分成:
Hello→ Latin世界→ Hanあい→ Hiragana
HarfBuzz 文本塑形时,一次只能处理同一 script 的字符串,因为不同 script 的文字系统,有不同的排版规则,比如:

HarfBuzz内部会根据script选取不同的shaping规则。
1.2 cluster
cluster 是HarfBuzz中的概念,表示一组不可分割的字符序列。单个字母或符号可以是一个cluster,连字之后的字形也可以视为一个cluster,比如f + i连字后变成fi,fi也是一个cluster。
1.3 grapheme
grapheme是Unicode中的概念,表示书写系统中的最小单位,比如单个的字母、符号、表情等,都是一个grapheme。
cluster与grapheme的区别:
二者没有相互关系,也不是一一对应的。举个例子,f + i连字后变成fi,那最终在HarfBuzz塑形完后只有fi 一个cluster,但是底层仍然是 f 和 i 两个grapheme构成的。
HarfBuzz中一般关注的是cluster。
1.4 blob
blob 是一个抽象概念,表示一段二进制数据的容器,用来管理原始数据的生命周期和权限,在HarfBuzz中用 hb_blob_t 结构体表示。
blob 的主要作用是封装字体文件的原始数据,当我们需要加载字体传给HarfBuzz时,这些字体文件一般会被加载到一个blob对象中。
blob 只关心数据本身,不理解数据的含义,也就是说blob并不知道这是一个字体文件,更不知道里面有什么表,它只负责管理对应内存的生命周期,确保数据在被使用期间是可访问的。
1.5 face
face 表示一个单独的字体,它会解析blob中的二进制字体数据,通过face可以访问字体中的各种table,如GSUB、GPOS、cmap表等,在HarfBuzz中用 hb_face_t 结构体表示。
需要注意的是,face是不带字号、缩放和其他渲染参数的,因此face无法直接用于塑形。
如果要塑形的话,需要通过face创建font。
1.6 font
font 表示字体实例,可以在face的基础上,设置字号、缩放等feature来创建一个font,在HarfBuzz中用 hb_font_t 结构体表示。
HarfBuzz 的核心排版函数 hb_shape() 接受的是 hb_font_t 对象,而不是 hb_face_t,这是因为 HarfBuzz 在计算字形位置和前进量时,需要知道字体的大小和比例。
1.7 buffer
buffer 在HarfBuzz中表示输入输出的缓冲区,用 hb_buffer_t 结构体表示。
比如我们在调用塑形函数** **hb_shape() 时,我们的输入字符串及其属性(方向、script、语言等)都是通过 buffer 完成的,塑形完成后,塑形结果也会通过 buffer 将字形及位置信息返回。
1.8 user data
上面提到的 hb_blob_t、hb_face_t、hb_font_t、hb_buffer_t 结构体,都有类似set_user_data()和get_user_data()的方法,主要作用是方便携带用户上下文。
1.9 小结
前面我们介绍了hb_blob_t、hb_face_t、hb_font_t、hb_buffer_t等概念,这些在HarfBuzz中被称为对象类型(注意并非OOP中面向对象的概念)。
在HarfBuzz中,所有对象类型都提供了特定的生命周期管理API,对象采用引用计数方式管理生命周期,通过各种create() 方法构建,初始创建时,引用计数为1,通过 reference() 方法引用(引用计数+1),通过 destroy() 方法解除引用(引用计数-1),当引用计数为0时释放。
比如,hb_buffer_t 对象可以通过 hb_buffer_create() 创建,通过 hb_buffer_reference() 引用,通过 hb_buffer_destroy() 解除引用。
HarfBuzz 所有对象的生命周期管理 API 都是线程安全的(除非你从源代码编译 HarfBuzz 时使用了HB_NO_MT配置标志),即便对象整体并非线程安全,引用(reference())或销毁(destroy())NULL 值也是允许的。
二、塑形操作
塑形大多以来字体中的 GSUB 和 GPOS 表,这一节我们来看塑形过程中的常见的操作:
1)字形替换
- 一对一替换(Simplified Forms):根据feature、语言设置的不同,会进行简体 ↔ 繁体、半角 ↔ 全角、普通 ↔ 装饰体等的转换。比如在日文中会将标点符号「。」替换为它的全角版本「。」
- 多对一替换/连字(Standard Ligatures):比如把 f + i → 合成为 fi 连字
- 一对多替换/分解(Glyph Composition/Decomposition):把一个“复合”字形拆解成多个独立的字形,通常用于预处理阶段,主要为了后续定位、重音调整等更方便,一般不会影响最终的视觉效果;比如某些字体可能将预组合的 é 字形分解为
e + ´ - 上下文替换(Initial Forms/Medial Forms/Final Forms/Isolated Forms):根据字形在单词中的位置来替换,比如阿拉伯文字母
ه会根据其在词首、词中、词尾或孤立出现,被替换为هـ、ـهـ、ـه、ه四种完全不同的形态 - 辅音连缀(Conjunct Forms):比如
क+्+ष会被替换字形为क्ष
2)字形定位:不修改字形,但是会影响position
- 字距调整(Kern):比如
T与o相邻时o的 x 偏移量为负,使它更靠近T - 标记定位(Mark):带重音符的如
é,会将´的前进量设为 0,并设置y_offset使其移动到e的正上方 - 草书连接 (Cursive Attachment):在“草书”类文字中(如阿拉伯文 Arabic、叙利亚文 Syriac、南印度文等),字符之间并不是并排放置的,而是通过笔画自然地连接起来的
3)字形重排
主要用于印度系文字,比如逻辑顺序上 辅音क+ 元音ि 时,元音在视觉顺序上需要重排到辅音的左侧,即: कि
总结
本文是从0到1自定义富文本渲染的原理篇之一,此外你还可能感兴趣:
- 一文读懂字符与编码
- 一文读懂字符、字形、字体
- 一文读懂字体文件
- 从0到1自定义文字排版引擎:原理篇
- 逆向分析CoreText中的字体级联/Font Fallback机制
- 新手小白也能看懂的LLDB技巧/逆向技巧
- HarfBuzz概览
更多内容可订阅公众号:非专业程序员Ping
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/947181.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!