[HarfBuzz] HarfBuzz API 设计
Posted alen_xie
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[HarfBuzz] HarfBuzz API 设计相关的知识,希望对你有一定的参考价值。
说明:
Harfbuzz 是一个开源的text opentype layout 引擎,它被应用于很多的开源项目中,如Pango,Filefox,Webkit,android等。
这份文档是Harfbuzz 的作者Behdad Esfahbod 完成用于说明新版的harfbuzz (harfbuzz-ng) API 设计思路的。
这份文档翻译自harfbuzz的邮件列表。由日期,我们可以看到,这份文档是2009年完成的,因而,这份文档其实并不能完全反映harfbuzz-ng code的当前状况,甚至可以说差异还有点大。
目前harfbuzz-ng已经被porting到Pango,Webkit等项目中,因而harfbuzz-ng 的用法,大致也可以从这些项目中找到一点蛛丝马迹。从code中的那些demo里,我们也可以学习一点harfbuzz-ng 的API。
有一些地方,实在是有些看不明白,或不知如何翻译,因而也就留原文在那里,供参考。
Behdad Esfahbod behdad at behdad.org
Tue Aug 18 16:23:50 PDT 2009
Previous message: [HarfBuzz] New Indic standard?
Next message: [HarfBuzz] HarfBuzz API design
Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
[警告: 这是一封很长的mail] Hi all, 随着重写的Harfbuzz OpenType Layout 引擎在最近被合并进pango主分支,我已经为公用API 而工作了许多个星期了。尽管仍然存在一些问题,但我已完成了大部分的设计,并很高兴能得到反馈。可以在下面的位置浏览当前的code:
http://git.gnome.org/cgit/pango/tree/pango/opentype
未来我将在那个目录下另外添加一个configure.ac文件,以使它可以作为一个独立的library来编译。两周之后,我也许会把它移回它自己的git repo,并使用git 魔法来把它pull进pango,直到我们开始把它作为一个共享库来使用(期待是在年底)。
设计HarfBuzz API 时,我参考了cairo。即是,可用性被列为最高优先级来考量。此外,隐藏技术细节、保持强大的功能而在内部实现高级特性,是API的其他一些目标。
这封mail中,我将只讨论backend-agnostic(后端不可知,无需关心API的实现的)API,那些我期待多数用户将会使用的API。也是用户通过包含"hb.h"可以访问到的那些API。例如,OpenType-specific APIs将只包含在"hb-ot.h"中,包括查询所支持的OpenType scripts, language systems, features, 等的列表API 。
最后,API的另一个严格的目标是完全的线程安全。那意味着,我不得不忍痛添加引用计数API。对象的生命周期API像cairo的,每一个对象都有: _create(), _reference(), _destory(), 和 _get_reference_count()。在某些时候,我们也许还想要添加_[gs]et_user_data(), 这对language bindings有用。
错误处理设计的也有点像cairo的,即,对象在内部记录failure (包括malloc failure), 但与cairo不同的是,没有直接的方法来查询对象的errors。HarfBuzz只是尽力去为你获取你想要的结果。但在出现errors的情况下,输出可能是错误的,但已经无法做的更好的了。总之没有很多办法来报告那种状态。所以,没有错误处理的API。
在介绍API之前,让我先来介绍一个我添加的内存管理的结构:
Blobs
hb_blob_t是一个用于管理原始数据的含有引用计数的容器,为了使HarfBuzz和用户之间的内存管理变得简单和灵活而被引入。Blobs 可以像这样来创建:
typedef enum
HB_MEMORY_MODE_DUPLICATE,
HB_MEMORY_MODE_READONLY,
HB_MEMORY_MODE_WRITABLE,
HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE
hb_memory_mode_t;
typedef struct hb_blob_t hb_blob_t;
hb_blob_t *
hb_blob_create (const char *data,
unsigned int length,
hb_memory_mode_t mode,
void *user_data,
hb_destroy_func_t destroy);
各个不同的mode参数的含义为:
- DUPLICATE: 立即复制数据并拥有它。
- READONLY: 传入的数据可以被抓住并将在随后使用,但是不应该被修改。如果需要修改,blob将会简单的复制数据。
- WRITEABLE: 数据是可写的,可自由地使用。
- READONLY_NEVER_DUPLICATE: 数据是只读的,并且不应被复制。这禁掉了需要对数据进行写访问的操作。
- READONLY_MAY_MAKE_WRITEABLE: 数据是只读的,但是可通过使用mprotect()或等价的win32 调用来使其成为可写的。它由用户来确保对数据调用mprotect()或特定于系统的等价的接口是安全的。实际上,在Linux和(根据Tor) win32上,那从来都不会称为一个问题。
用户也可以创建一个blob的子blob:
hb_blob_t *
hb_blob_create_sub_blob (hb_blob_t *parent,
unsigned int offset,
unsigned int length);
在锁定了Blob的数据之后就可以进行访问了:
const char *
hb_blob_lock (hb_blob_t *blob);
用户可检查数据是否可写:
hb_bool_t
hb_blob_is_writeable (hb_blob_t *blob);
可以在适当的地方请求将其变为可写的:
hb_bool_t hb_blob_try_writeable_inplace (hb_blob_t *blob);
或者可以请求使数据变为可写的,如果需要则创建一份拷贝:
hb_bool_t
hb_blob_try_writeable (hb_blob_t *blob);
对于后一种情况,blob必须是没有被锁定的。锁是递归的。blob内部成员使用一个mutex来保护,因此这个结构是线程安全的。
blob的主要用途为提供font data或者table data给HarfBuzz。更多信息请参见后文。
Text API
也许老的基于QT HarfBuzz shaper API 的API和新的API最大的不同之处在于,新的API复用了hb-buffer同时用于shaping的输入+输出。因而,你将像下面这样使用harfbuzz:
- 创建 buffer
- 向buffer中添加文本 ---> 现在buffer中包含Unicode文本
- 将buffer作为参数调用 hb_shape()
- 使用输出的glyphs ---> 现在buffer中包含位置经过调整的glyphs
- hb_buffer_t: 保存 文本/glyphs,并且不是线程安全的
- hb_face_t: 代表单个SFNT face,完全的线程安全的,映射到cairo_font_face_t.
- hb_font_t: 代表具有某一hinting选项某一字体大小下的face,完全的线程安全的,映射到cairo_scaled_font_t。
Buffer
buffer的输出是两个数组:glyph infos和glyph positions。最终这两个结构将看上去像下面这样:
typedef struct hb_glyph_info_t
hb_codepoint_t codepoint;
hb_mask_t mask;
uint32_t cluster;
/*< private >*/
hb_var_int_t var1;
hb_var_int_t var2;
hb_glyph_info_t;
typedef struct hb_glyph_position_t
hb_position_t x_advance;
hb_position_t y_advance;
hb_position_t x_offset;
hb_position_t y_offset;
/*< private >*/
hb_var_int_t var;
hb_glyph_position_t;
使用hb-buffer用于输入所带来的一个好处是,现在我们可以通过实现如下接口来简单的添加UTF-8,UTF-16和UTF-32的 APIs:
void
hb_buffer_add_utf8 (hb_buffer_t *buffer,
const char *text,
int text_length,
unsigned int item_offset,
int item_length);
void
hb_buffer_add_utf16 (hb_buffer_t *buffer,
const uint16_t *text,
int text_length,
unsigned int item_offset,
int item_length);
void
hb_buffer_add_utf32 (hb_buffer_t *buffer,
const uint32_t *text,
int text_length,
unsigned int item_offset,
int item_length);
它们向buffer中添加单独的Unicode字符,并分别设置cluster值。
Face
HarfBuzz是围绕着SFNT font格式而被创建起来的。一个Face简单的表示一个SFNT face,尽管这对于用户是完全透明的:你可以将无效的数据作为font data传给Harfbuzz ,但HarfBuzz会简单的忽略它。有两个主要的face构造器:
hb_face_t *
hb_face_create (hb_blob_t *blob,
unsigned int index);
typedef hb_blob_t * (*hb_reference_table_func_t) (hb_face_t *face, hb_tag_t tag, void *user_data);
/* calls destroy() when not needing user_data anymore */
hb_face_t *
hb_face_create_for_tables (hb_reference_table_func_t reference_table_func,
void *user_data,
hb_destroy_func_t destroy);
for_tables()版本使用一个回调来load SFNT tables,而不带for_tables()的版本需要一个包含font文件数据的blob,加上TTC 集合中的face 索引。
目前face只负责shaping的“复杂的”部分,即, OpenType Layout features (GSUB/GPOS...)。未来我们也许也会直接访问cmap。现在没有实现,但老式风格的 'kern' table 将也会在想同的层次来实现。
引入blob机制的原因是,新的OpenType Layout 引擎以及我们将会添加的其他的表工作直接使用font 数据,而不是把它解析到分离的数据结构中。因此,我们需要首先"sanitize" (审查)font数据。当sanitizing(审查)时,不是仅仅给出pass/fail的结果,而是依据发现的错误(比如,一个指向了超出了table的边界的偏移量),我们也许会修改font数据以使它足够正确,从而可以传递给layout code。在那些情况下,我们首先尝试使blob变得可写,如果失败,则创建它的一个可写的副本。即简单或复杂的写时复制。对于正常的fonts,这意味着per-process的零内存消耗。未来我们将在fontconfig中缓存 sanitize()的结果,以便于不是每一个process都不得不sanitize() clean fonts。
Font
通常我宁愿font 构造器只有一个hb_face_t 参数(像cairo所做的那样)。一个font是具有某些hinting或其他选项的某一字体大小下的一个face。然而,由于FreeType缺少引用计数,而使这变得很困难。原因是:Pango基于FT_Face实例的通用槽来缓存hb_face_t。然而,一个hb_font_t应该被关联到一个PangoFont或PangoFcFont。
正如每个人都了解的,FT_Face不是线程安全的,它没有引用计数,并且也不仅仅是一个face,它还包含一个font某一时刻的字体大小的信息。由于这个原因,无论何时一个font想要访问一个FT_Face,它都需要先“lock”。虽然你lock了它,但你获取的对象不一定与上次获取的相同As everyone knows, FT_Face is not threadsafe, is not refcounted, and is not just a face, but also includes sizing information for one font at a time. For this reasons, whenever a font wants to access a FT_Face, it needs to "lock" one. When you lock it though, you don't necessarily get the same object that you got the last time. It may be a totally different object, created for the same font data, depending on who manages your FT_Face pool (cairo in our case). Anyway, for this reason, having hb_font_t have a ref to hb_face_t makes life hard: one either would have to create/destroy hb_font_t betweenFT_Face lock/unlock, or risk having a hb_face_t pointing to memory owned by a FT_Face that may have been freed since.
For the reasons above I opted for not refing a face from hb_font_t and instead passing both a face and a font around in the API. Maybe I should use a different name (hb_font_scale_t?) I'd rather keep names short, instead of cairo style hb_font_face_t and hb_scaled_font_t.
Anyway, a font is created easily:
hb_font_t *
hb_font_create (hb_face_t *face);
One then needs to set various parameters on it, and after the last change, it can be used from multiple threads safely.
Shaping
当前我确定的主要的 hb_shape() API 如下:
typedef struct hb_feature_t
hb_tag_t tag;
uint32_t value;
unsigned int start;
unsigned int end;
hb_feature_t;
void
hb_shape (hb_font_t *font,
hb_buffer_t *buffer,
const hb_feature_t *features,
unsigned int num_features);
features 参数通常为空,但也可以被用于传递像下面的这些东西:
- "kern"=>"0" -------> no kerning
- "ot:aalt"=>"2" -------> use 2nd OpenType glyph alternative
- "ot:mkmk"=>"0" -------> never apply 'mkmk' OpenType feature
- "ot:script"=>"math" ------> Force an OpenType script tag
- "ot:langsys"=>"FAR " -----> Force an OpenType language system
- "ot"=>"0" ------> Disable OpenType engine (prefer AAT, SIL, etc)
- 或也许甚至是标记文本的可视边界的features等。
Discussion
Script and language
调用shape()通常需要更多的信息。也就是:
text direction,script,和language。注意,那些都不属于face 或 font 对象。对于text direction,我很确信它应该设给buffer,并且在shaping时,该值已经设置好了。
对于script和language,则稍微有点微妙。我同样确信它们属于buffer。对于script,这很好,但对于language,这引入了一个实现上的麻烦:即我将不得不处理language tag的复制/interning, 一些我尝试去避免的事情。另一些选择是:
- 为hb_shape()添加额外的参数。我宁愿不这样做。在主API 之外保持这样的细节,在适当的位置添加setters使API 更干净,也更可扩展。
- 为它们使用feature dict。我非常反对这种做法。对于我来说,feature dict已经太highlevel了。
因此,此处的问题,很欢迎收到comments。
Unicode callbacks
Harfbuzz 本身不包含任何Unicode 字符的数据库表,但却需要访问一些属性,其中的一些只用于fallback shaping。目前我已经确定如下的属性在某些地方是有用的:
typedef hb_codepoint_t (*hb_unicode_mirroring_func_t)(
hb_unicode_funcs_t *ufuncs,
hb_codepoint_t unicode,
void *user_data);
需要实现字符级的mirroring。
typedef hb_unicode_general_category_t (*hb_unicode_general_category_func_t)(
hb_unicode_funcs_t *ufuncs,
hb_codepoint_t unicode,
void *user_data);
当face没有GDEF glyph classes时,用于合成它们。
typedef hb_script_t (*hb_unicode_script_func_t)(hb_unicode_funcs_t *ufuncs,
hb_codepoint_t unicode,
void *user_data);
除非我们也实现了script itemization(我们可以透明的来完成,比如,如果用户给shape()函数传入了SCRIPT_COMMON),否则我们不需要它。
typedef hb_unicode_combining_class_t (*hb_unicode_combining_class_func_t)(
hb_unicode_funcs_t *ufuncs,
hb_codepoint_t unicode,
void *user_data);
当GPOS不可用时,为mark positioning分类有用。
typedef unsigned int (*hb_unicode_eastasian_width_func_t)(
hb_unicode_funcs_t *ufuncs,
hb_codepoint_t unicode,
void *user_data);
不确定它在Harfbuzz 层是否有用。最近,在Pango中,垂直方向情况下我需要用它来设置正确的文本。
我已经添加了一个称为 hb_unicode_funcs_t的对象,它包含所有的这些回调。它可以被引用,也可以被复制。还有一个 hb_unicode_funcs_make_immutable() 调用,对于那些想要放出一个 它们自己拥有的hb_unicode_funcs_t对象的引用,又想要确保用户不会错误的修改那个对象的libraries有用。
然后hb-glib.h层实现:
hb_unicode_funcs_t *
hb_glib_get_unicode_funcs (void);
接下来的问题是,在哪儿将unicode funcs传给shape()机制。我当前的设计是设置给face:
void
hb_face_set_unicode_funcs (hb_face_t *face,
hb_unicode_funcs_t *unicode_funcs);
然而那是相当武断的。face中并没有什么地方是单独需要Unicode 功能的。此外,我想要保持face的 objective。例如,你应该能够从任何可以获取一个 hb_face_t的地方(pango...)获取它,并且可以在无须担心它的设置的情况下去使用它。 Unicode funcs,虽然定义良好,但仍然可以从许多地方来获取: glib, Qt, Python的,你自己的实验,...
我开始考虑把它移到buffer里。那是仅有的 Unicode的其他的入口 (add_utf8/...),并且buffer是仅有的不被 HarfBuzz共享的对象,所以,用户对它有完全的控制权。
有人可能会问,一开始为什么要使得回调是可设置的呢?我们可以在编译时硬编码它们:如果 glib可用, 就使用它,否则使用我们自己的拷贝或其它什么东西。尽管我也许会使编译时可用的作为备用品,但是我想要使用户可以自己设置回调。最少直到我写了一个 UCD库来管理它们 ...
因而那是另外一个我需要得到反馈的问题。
Font callbacks
这些是我已经写出原型的 font callbacks (font class, font funcs, ...)。注意, font,face,和一个 user_data 参数会同时传给它们。技术上而言,这些回调中的一些只需要一个 face,不需要 font, 但由于许多系统在实际的font,而不是face之上实现这些函数,我们需要以现在的这种方式来实现。当前我们可以给 hb-font设置 hb_font_callbacks_t对象和 user_data (hb_font_set_funcs())。
typedef hb_bool_t (*hb_font_get_glyph_func_t)(hb_font_t *font, void *font_data,
hb_codepoint_t unicode,
hb_codepoint_t variation_selector,
hb_codepoint_t *glyph,
void *user_data);
这是 cmap回调。注意 variant_selector:它支持 cmap14表。对于老式的客户端,它们可以忽略那个参数,并做映射。我们也许将会内部实现对于 Unicode cmaps,但对于丢失的 glyphs或者找不到合适的 cmap的情况,可以使用这个函数。那有三个好处:
- Pango etc can pass whatever code they want for missing glyphs, to use later to draw hexbox,
- Pango, through fontconfig, knows how to handle non-Unicode cmaps, so that will continue to work,
- For non
以上是关于[HarfBuzz] HarfBuzz API 设计的主要内容,如果未能解决你的问题,请参考以下文章
通过 Harfbuzz 和 FreeType 在文本编辑器中显示混合复杂脚本
用pdb.set_trace()设断点,跟nova/api/openstack/compute/servers.py - detail() 流程