搞跨端渲染?你绕不开的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
搞跨端渲染?你绕不开的HarfBuzz原理的更多相关文章
- React / Vue 跨端渲染原理与实现探讨
跨端渲染是渲染层并不局限在浏览器 DOM 和移动端的原生 UI 控件,连静态文件乃至虚拟现实等环境,都可以是你的渲染层.这并不只是个美好的愿景,在今天,除了 React 社区到 .docx / .pd ...
- 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十五║初探SSR服务端渲染(个人博客二)
缘起 时间真快,现在已经是这个系列教程的下半部 Vue 第 12 篇了,昨天我也简单思考了下,可能明天再来一篇,Vue 就基本告一段落了,因为什么呢,这里给大家说个题外话,当时写博文的时候,只是想给大 ...
- 实例PK(Vue服务端渲染 VS Vue浏览器端渲染)
Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...
- Vue服务端渲染和Vue浏览器端渲染的性能对比
Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...
- Unity应用架构设计(10)————绕不开的协程和多线程(Part 1)
在进入本章主题之前,我们必须要了解客户端应用程序都是单线程模型,即只有一个主线程(Main Thread),或者叫做UI线程,即所有的UI控件的创建和操作都是在主线程上完成的.而服务器端应用程序,也就 ...
- Vue.js 服务端渲染业务入门实践
作者:威威(沪江前端开发工程师) 本文原创,转载请注明作者及出处. 背景 最近, 产品同学一如往常笑嘻嘻的递来需求文档, 纵使内心万般拒绝, 身体倒是很诚实. 接过需求,好在需求不复杂, 简单构思 后 ...
- 6.前端基于react,后端基于.net core2.0的开发之路(6) 服务端渲染(SSR)
0.源码地址 https://gitee.com/teambp/ScaffoldClient 这个地址下载对应源码. 1.服务端渲染是啥? 就是在服务器进行页面渲染(废话),当页面展示后,显示的就是 ...
- (译文)React----React应用程序流式服务端渲染
好处 React16推出了流式服务端渲染,它允许你并行地分发HTML片段.这样可以让渲染 出的首字节有意义的内容给用户速度更快. (例子1,上面部分是一次性转换,下面是流渲染,两种方式) 而且相对re ...
- 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二九║ Nuxt实战:异步实现数据双端渲染
回顾 哈喽大家好!又是元气满满的周~~~二哈哈,不知道大家中秋节过的如何,马上又是国庆节了,博主我将通过三天的时间,给大家把项目二的数据添上(这里强调下,填充数据不是最重要的,最重要的是要配合着让大家 ...
- 教你如何在React及Redux项目中进行服务端渲染
服务端渲染(SSR: Server Side Rendering)在React项目中有着广泛的应用场景 基于React虚拟DOM的特性,在浏览器端和服务端我们可以实现同构(可以使用同一份代码来实现多端 ...
随机推荐
- 为什么i++不是原子操作?一个让无数并发程序崩溃的“常识”
原子性:不可分割的操作 private int count = 0; public void test() { List<Thread> ts = new ArrayList<> ...
- Python的设计哲学是优雅、明确、简单;Python开发者的哲学是用一种方法,最好是只有一种方法来做一件事
https://blog.csdn.net/weixin_37988176/article/details/109373023 脚本语言泛指单用作简单編程任务如shell scripts.脚本语言是一 ...
- Word文档的标题总是自动出现首行缩进怎么办?
本文介绍在Word中,标题样式跟随正文样式呈现首行缩进状态的解决办法. 临近毕业季,马上就要开始写毕业论文了:我开始写得比较早,目前已经完成了绝大部分--写到1.1引言部分了.写的过程中发现了 ...
- 【MySQL】2.细节知识
1.存储引擎 MySQL体系结构 连接层:最上层的客户端连接服务,完成连接处理.授权认证等服务 服务层:完成大多数核心服务功能,并完成缓存的查询,SQL的分析和优化,部分内置函数执行 引擎层:负责My ...
- 文生绘动 Agent:从词语到动态影像,言出即成,你的AI动画创作伙伴
文生绘动 Agent:从词语到动态影像,言出即成,你的AI动画创作伙伴 1.一款由大型语言模型(LLM)驱动的动画引擎 agent . 一款由大型语言模型(LLM)驱动的动画引擎 agent .用户输 ...
- Java面向对象基础——10.内部类
目录 Java内部类总结 1. 内部类(Inner Class) 2. 匿名类(Anonymous Class) 3. 静态内部类(Static Nested Class) 共性与差异 Java内部类 ...
- 如何做一个纯净版的ABP vNext 脚手架
大家好,我是张飞洪,专注.NET开发十来年.感谢您的阅读,我会不定期分享我的学习心得和职场经验,希望我的文章能成为你成长路上的助力.让我们一起精进,共同进步. 今天想和分享的是如何基于一套自定义的模版 ...
- 亚洲No.1设计学院作品鉴赏
- for (int num : nums)的理解
1.理解 for (int num : nums) 是 C++11 引入的一种新的循环语法,称为范围-based for 循环或者是 foreach 循环.这种循环语法可以遍历容器或者数组中的元素,并 ...
- .net ocre 程序崩溃自动dump在多平台中的实现
前言 经常排查问题的朋友都知道,我们在遇到CPU或者内存高的时候,有时会生成dump文件来做分析.但是我们也会遇到一些场景,应用程序直接崩溃退出,这个时候我们已经没法使用常规方式dump了,因为整个进 ...