说明: android
Harfbuzz 是一个开源的text opentype layout 引擎,它被应用于不少的开源项目中,如Pango,Filefox,Webkit,android等。 git
这份文档是Harfbuzz 的做者Behdad Esfahbod 完成用于说明新版的harfbuzz (harfbuzz-ng) API 设计思路的。 数据库
这份文档翻译自harfbuzz的邮件列表。由日期,咱们能够看到,这份文档是2009年完成的,于是,这份文档其实并不能彻底反映harfbuzz-ng code的当前情况,甚至能够说差别还有点大。 后端
目前harfbuzz-ng已经被porting到Pango,Webkit等项目中,于是harfbuzz-ng 的用法,大体也能够从这些项目中找到一点蛛丝马迹。从code中的那些demo里,咱们也能够学习一点harfbuzz-ng 的API。 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的其余一些目标。 app
这封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以前,让我先来介绍一个我添加的内存管理的结构:
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参数的含义为:
用户也能够建立一个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的输出是两个数组: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 参数一般为空,但也能够被用于传递像下面的这些东西:
调用shape()一般须要更多的信息。也就是:
text direction,script,和language。注意,那些都不属于face 或 font 对象。对于text direction,我很确信它应该设给buffer,而且在shaping时,该值已经设置好了。
对于script和language,则稍微有点微妙。我一样确信它们属于buffer。对于script,这很好,但对于language,这引入了一个实现上的麻烦:即我将不得不处理language tag的复制/interning, 一些我尝试去避免的事情。另外一些选择是:
所以,此处的问题,很欢迎收到comments。
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 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的状况,可使用这个函数。那有三个好处:
typedef hb_bool_t (*hb_font_get_glyph_contour_point_func_t)(hb_font_t *font, void *font_data, hb_codepoint_t glyph, unsigned int point_index, hb_position_t *x, hb_position_t *y, void *user_data);复杂的 GPOS positioning须要它。
Needed for complex GPOS positioning. Pango never did this before. Pretty straightforward, just need to make it clear the space that the positions are returned in. I'll discuss that in the next section.
typedef void (*hb_font_get_glyph_metrics_func_t) (hb_font_t *font, hb_face_t *face, const void *user_data, hb_codepoint_t glyph, hb_glyph_metrics_t *metrics);
This one is a bit more tricky. Technically we just need the advance width. The rest of the metrics are only used for fallback mark positioning. So maybe I should split this in a get_glyph_advance and a full get_glyph_metrics one. Current HarfBuzz has a single call to get advance width of multiple glyphs. If that kind of optimization deems necessary in the future, we can add a callback to take an entire buffer and set the advances.
如今仍然有以下问题
typedef hb_position_t (*hb_font_get_glyph_kerning_func_t)(hb_font_t *font, void *font_data, hb_codepoint_t first_glyph, hb_codepoint_t second_glyph, void *user_data);
Again, most probably we will read 'kern' table internally anyway, but this can be used for fallback with non-SFNT fonts. You can even pass, say, SVG fonts through HarfBuzz such that the higher level just deals with one API.
Another call that may be useful is a get_font_metrics one. Again, only useful in fallback positioning. In that case, ascent/descent as well as slope come handy.
目前,基于老的code,font对象具备以下的setters:
void hb_font_set_scale (hb_font_t *font, int x_scale, int y_scale); /* * A zero value means "no hinting in that direction" */ void hb_font_set_ppem (hb_font_t *font, unsigned int x_ppem, unsigned int y_ppem);
ppem API是定义明确的:那是用于 hinting和 device-dependent定位的 ppem。老的 HarfBuzz也有一个 "device-independent"设定,但那须要关掉 hinting。我已经移除那个设定以支持传递0做为 ppem。那容许在一个方向的 hinting,而不是另外一个。不像老的 HarfBuzz,咱们将本身作 metrics hinting。
set_scale() API在 FreeType以后建模,但使用起来仍然是笨拙的。 与HarfBuzz有关有四个不一样的空间:
Now, what the hb_font_set_scale() call accepts right now is a 16.16 pair of scales mapping from font design space to device space. I'm not sure, but getting that number from font systems other than FreeType may actually be quite hard. The problem is, upem is an implementation detail of the face, and the user shouldn't care about it.
So my proposal is to separate upem and make it a face property. In fact, we can read upem from OS/2 SFNT table and assume a 1024 upem for non-SFNT fonts (that's what Type1 did IIRC). In fact, we wouldn't directly use upem for non-SFNT fonts right now. Then the scale would simply need to map EM space to device space. But notice how that's the same as the ppem. But then again, we really just care about user space for positioning (device space comes in only when hinting). So, set_scale should be changed to accept em-to-user-space scale. Not surprisingly, that's the same as the font size in the user-space.
这里我须要解决的另外一个问题是, cairo容许一个彻底的 matrix来完成设备空间到用户空间的转换。即,好比 glyphs能够就地的被旋转。那也是咱们用来实现竖直文本的方法。我倾向于也添加一个 full-matrix setter。其行为将是:
In that model however, I wonder how easy/hard would it be for callbacks to provide requested values (contour point, glyph metrics, etc) in the user space. For cairo/pango I know that's actually the easiest thing to do, anything else would need conversion, but I'm not sure about other systems. An alternative would be to let the callbacks choose which space the returned value is in, so we can map appropriately.
I guess that's it for now. Let discussion begin. Thanks for reading!
behdad