harfbuzz-ng如何選擇一個shaper
harfbuzz-ng在shape文字時,依然是會根據(jù)文字的特點,選中一個種shaper,然后再將實際shape的過程都交給shaper來處理這樣。只是在harfbuzz-ng中,不像在老版harfbuzz中那樣,是簡單的依據(jù)所傳入字串的script,通過一個表就直接地選定一個shaper,而是在參考字串的特性(script)之外,還會參考字庫文件本身的特征,來最終選定一個shaper。同時,harfbuzz-ng中的shaper與老版harfbuzz中的shaper在概念上也有一定的區(qū)別。老版harfbuzz中的shaper基本上都是針對某種特定的語言而實現(xiàn),并借助于內(nèi)部的OpenType處理功能,來提供OpenType的高級渲染操作,可以稱為是語言shaper吧。而在harfbuzz-ng中,其shaper則主要包括Graphite2 shaper,OpenType shaper這樣的一些,可以稱為是字庫shaper吧。借助于harfbuzz-ng這樣的一種結(jié)構(gòu),用戶可以只為harfbuzz-ng編寫一個客戶端,然后簡單的將Graphite2之類的其他shape engine接到harfbuzz-ng下面,以實現(xiàn)對字串的最優(yōu)化shaping。harfbuzz-ng本身主要是實現(xiàn)了OpenType shaper,因而下面我們也會更多的關(guān)注與OpenType shaper有關(guān)的一些內(nèi)容。下面我們就來看一下,在harfbuzz-ng中,選擇shaper的邏輯是怎樣的吧。當然,下面的code分析,一定是基于harfbuzz某個特定的版本的,這個版本實際上是0.9.10,harfbuzz目前都還依然處于開放狀態(tài)中,本文的分析對于未來的某些版本也可能會有不適用的狀況。
harfbuzz-ng shape文本的主流程
首先,來看一下harfbuzz-ng的主入口函數(shù)hb_shape():
void
hb_shape (hb_font_t *font,
hb_buffer_t *buffer,
const hb_feature_t *features,
unsigned int num_features)
{
hb_shape_full (font, buffer, features, num_features, NULL);
}
這個函數(shù)接收font和buffer參數(shù),其中font里面包含有與字體相關(guān)的信息,比如所使用的字庫文件的內(nèi)容,字體的大小等;而buffer中則包含有關(guān)于字串的信息,比如字串的內(nèi)容,字串的方向和script等。而這個函數(shù)的?features??和?num_features?參數(shù)常常是NULL和0,因為客戶端通常都不需要自己來確定shape一個字串所需要的features嘛,選擇到底要使用那些features的工作,完全交給harfbuzz-ng來就行了。完成對字串的shaping之后,結(jié)果會通過傳入的buffer參數(shù)返回給調(diào)用者。
可以看到hb_shape()的定義倒是簡單的很,就只是調(diào)用了hb_shape_full()來完成所有的工作而已。接下來來看一下hb_shape_full()的實現(xiàn):
hb_bool_t
hb_shape_full (hb_font_t *font,
hb_buffer_t *buffer,
const hb_feature_t *features,
unsigned int num_features,
const char * const *shaper_list)
{
if (unlikely (!buffer->len))
return true;
assert (buffer->content_type == HB_BUFFER_CONTENT_TYPE_UNICODE);
buffer->guess_segment_properties ();
hb_shape_plan_t *shape_plan = hb_shape_plan_create_cached (font->face, &buffer->props, features, num_features, shaper_list);
hb_bool_t res = hb_shape_plan_execute (shape_plan, font, buffer, features, num_features);
hb_shape_plan_destroy (shape_plan);
if (res)
buffer->content_type = HB_BUFFER_CONTENT_TYPE_GLYPHS;
return res;
}
可以看到這個函數(shù)對字串做shape的過程:
不過所謂的shape plan究竟是個什么東西呢?先來看一下hb_shape_plan_t的定義:
struct hb_shape_plan_t
{
hb_object_header_t header;
ASSERT_POD ();
hb_bool_t default_shaper_list;
hb_face_t *face;
hb_segment_properties_t props;
hb_shape_func_t *shaper_func;
const char *shaper_name;
struct hb_shaper_data_t shaper_data;
};
在harfbuzz-ng中,hb_face_t是一個字庫文件的抽象,harfbuzz-ng可以借助于這個對象來獲取字庫文件的一些內(nèi)容,比如獲取OpenType的表等;而hb_segment_properties_t則主要保存字串屬性相關(guān)的一些信息,包括script,direction和language等。可以看到,這個結(jié)構(gòu)前面的幾個字段,主要是與字庫文件(face)和字串屬性(props)有關(guān)的一些內(nèi)容,而后面的幾個字段,則是與選中的shaper有關(guān)的一些內(nèi)容。不難理解,創(chuàng)建shape plan的過程,大體上應(yīng)該是,用client傳入的參數(shù),設(shè)置前面的幾個字段(default_shaper_list,face和props),然后依據(jù)于client傳入的參數(shù),創(chuàng)建或者確定后面幾個字段(shaper_func,shaper_name和shaper_data)的內(nèi)容。接著,我們就來看一下,harfbuzz-ng到底是如何完成這一切的吧。
hb_shape_plan_create_cached() --- shape plan創(chuàng)建的主要流程
前面提到,hb_shape_plan_create_cached()函數(shù)創(chuàng)建shape plan,那么我們就先來看一下這個函數(shù)的定義:
hb_shape_plan_t *
hb_shape_plan_create_cached (hb_face_t *face,
const hb_segment_properties_t *props,
const hb_feature_t *user_features,
unsigned int num_user_features,
const char * const *shaper_list)
{
if (num_user_features)
return hb_shape_plan_create (face, props, user_features, num_user_features, shaper_list);
hb_shape_plan_proposal_t proposal = {
*props,
shaper_list,
NULL
};
if (shaper_list) {
/* Choose shaper. Adapted from hb_shape_plan_plan(). */
#define HB_SHAPER_PLAN(shaper) \
HB_STMT_START { \
if (hb_##shaper##_shaper_face_data_ensure (face)) \
proposal.shaper_func = _hb_##shaper##_shape; \
} HB_STMT_END
for (const char * const *shaper_item = shaper_list; *shaper_item; shaper_item++)
if (0)
;
#define HB_SHAPER_IMPLEMENT(shaper) \
else if (0 == strcmp (*shaper_item, #shaper)) \
HB_SHAPER_PLAN (shaper);
#include "hb-shaper-list.hh"
#undef HB_SHAPER_IMPLEMENT
#undef HB_SHAPER_PLAN
if (unlikely (!proposal.shaper_list))
return hb_shape_plan_get_empty ();
}
retry:
hb_face_t::plan_node_t *cached_plan_nodes = (hb_face_t::plan_node_t *) hb_atomic_ptr_get (&face->shape_plans);
for (hb_face_t::plan_node_t *node = cached_plan_nodes; node; node = node->next)
if (hb_shape_plan_matches (node->shape_plan, &proposal))
return hb_shape_plan_reference (node->shape_plan);
/* Not found. */
hb_shape_plan_t *shape_plan = hb_shape_plan_create (face, props, user_features, num_user_features, shaper_list);
hb_face_t::plan_node_t *node = (hb_face_t::plan_node_t *) calloc (1, sizeof (hb_face_t::plan_node_t));
if (unlikely (!node))
return shape_plan;
node->shape_plan = shape_plan;
node->next = cached_plan_nodes;
if (!hb_atomic_ptr_cmpexch (&face->shape_plans, cached_plan_nodes, node)) {
hb_shape_plan_destroy (shape_plan);
free (node);
goto retry;
}
/* Release our reference on face. */
hb_face_destroy (face);
return hb_shape_plan_reference (shape_plan);
}
可以看到,這個函數(shù)正是主要依據(jù)于face和props來創(chuàng)建shape plan的。這個函數(shù)中為兩種情況做了一些特殊的處理:第一種是num_user_features大于0的情況,即客戶端指定了一些features;第二種是shaper_list非空的情況,即客戶端已經(jīng)提供了一個shaper列表給harfbuzz-ng來選則適當?shù)膕haper。
第一種情況簡單明了,會直接調(diào)用hb_shape_plan_create()函數(shù)來創(chuàng)建shape plan,此處不再多說,后面會再來說明這個函數(shù)的定義。那就來看一下第二種情況的一些特殊處理:
if (shaper_list) {
/* Choose shaper. Adapted from hb_shape_plan_plan(). */
#define HB_SHAPER_PLAN(shaper) \
HB_STMT_START { \
if (hb_##shaper##_shaper_face_data_ensure (face)) \
proposal.shaper_func = _hb_##shaper##_shape; \
} HB_STMT_END
for (const char * const *shaper_item = shaper_list; *shaper_item; shaper_item++)
if (0)
;
#define HB_SHAPER_IMPLEMENT(shaper) \
else if (0 == strcmp (*shaper_item, #shaper)) \
HB_SHAPER_PLAN (shaper);
#include "hb-shaper-list.hh"
#undef HB_SHAPER_IMPLEMENT
#undef HB_SHAPER_PLAN
if (unlikely (!proposal.shaper_list))
return hb_shape_plan_get_empty ();
}
這段code看起來還真夠奇怪的。這都是些什么東西嘛。只是定義了兩個宏,HB_SHAPER_PLAN和HB_SHAPER_IMPLEMENT,然后外加一個什么也沒做的空循環(huán),在然后就是include了一個文件而已。難道include的那個文件暗藏玄機?沒錯,所有的秘密還確實都在那個文件里了。我們來看一下那個hb-shaper-list.hh的內(nèi)容:
#ifndef HB_SHAPER_LIST_HH
#define HB_SHAPER_LIST_HH
#endif /* HB_SHAPER_LIST_HH */ /* Dummy header guards */
/* v--- Add new shapers in the right place here. */
#ifdef HAVE_GRAPHITE2
/* Only picks up fonts that have a "Silf" table. */
HB_SHAPER_IMPLEMENT (graphite2)
#endif
#ifdef HAVE_OT
HB_SHAPER_IMPLEMENT (ot) /* <--- This is our main OpenType shaper. */
#endif
#ifdef HAVE_HB_OLD
HB_SHAPER_IMPLEMENT (old)
#endif
#ifdef HAVE_ICU_LE
HB_SHAPER_IMPLEMENT (icu_le)
#endif
#ifdef HAVE_UNISCRIBE
HB_SHAPER_IMPLEMENT (uniscribe)
#endif
#ifdef HAVE_CORETEXT
HB_SHAPER_IMPLEMENT (coretext)
#endif
HB_SHAPER_IMPLEMENT (fallback) /* <--- This should be last. */
至此,那兩個宏的作用看起來就清晰多了嘛。根據(jù)這個文件中的內(nèi)容,將前面看到的那兩個宏都解開來看那段code到底是什么:
if (shaper_list) {
for (const char * const *shaper_item = shaper_list; *shaper_item; shaper_item++)
if (0)
;
else if (0 == strcmp (*shaper_item, "graphite2"))
do {
if (hb_graphite_shaper_face_data_ensure (face))
proposal.shaper_func = _hb_graphite_shape;
} wihle (0);
else if (0 == strcmp (*shaper_item, "ot"))
do {
if (hb_ot_shaper_face_data_ensure (face))
proposal.shaper_func = _hb_ot_shape;
} wihle (0);
else if (0 == strcmp (*shaper_item, "fallback"))
do {
if (hb_fallback_shaper_face_data_ensure (face))
proposal.shaper_func = _hb_fallback_shape;
} wihle (0);
if (unlikely (!proposal.shaper_list))
return hb_shape_plan_get_empty ();
}
當然,這個也不一定就是前面那段code將宏解開的真實結(jié)果。由hb-shaper-list.hh的內(nèi)容,我們知道,for循環(huán)下的else-if block的數(shù)量和內(nèi)容,都會依賴于到底有多少種shaper是通過宏而被enabled起來的。不過,有兩種shaper是必定會被enabled的,一種是harfbuzz-ng實現(xiàn)的ot,另外一種就是fallback。
由這段code來看,前面所提到的針對第二種情況的特殊處理,其實也就是補足proposal中shape_func相關(guān)的信息,以便于后面在匹配shape plan時,能有更多的依據(jù)。
這段code會逐個的檢查傳進來的那個shaper_list中的shaper,以確定合適的shaper。它會調(diào)用shaper的hb_##shaper##_shaper_face_data_ensure()函數(shù),比如hb_ot_shaper_face_data_ensure()等,來檢查相應(yīng)的shaper是否能夠處理傳入的那個face(字庫文件),如果可以,則將相應(yīng)的shaper_func函數(shù)賦給proposal.shaper_func。
那么,在選擇shaper的時候為什么會需要對字庫做檢查呢?因為確實有一些shaper對字庫有特殊的要求,比如ot的shaper就要求傳入的字庫必須是一個OpenType字庫,而不能是簡單的TrueType字庫,graphite2的shaper則對字庫有更高的要求。
根據(jù)需要(shaper_list非空的那段code,通常都不會執(zhí)行到,shaper_list為空的時候多),對第二種情況作了proposal中shape_func相關(guān)信息的補充之后,hb_shape_plan_create_cached()函數(shù)就會從face對象中取出一個緩存的hb_face_t::plan_node_t鏈表(face->shape_plans),并檢查是否能夠找到proposal所描述的shape plan,若可以找到則將shape plan返回給調(diào)用者,創(chuàng)建shape plan的過程就算結(jié)束了。
若hb_shape_plan_create_cached()函數(shù)沒能在face的緩存中找到所需要的shape plan的話,則它就會調(diào)用hb_shape_plan_create()來創(chuàng)建一個,將這個shape plan緩存進face的shape_plans鏈表里去,并返回剛剛創(chuàng)建的這個shape plan。
hb_##shaper##_shaper_face_data_ensure (face) ?--- 對字庫文件做檢查
那個所謂的對字庫的檢查到底是如何進行的呢?以ot的shaper為例,來看一下字庫檢查都做了些什么事情。來看hb_ot_shaper_face_data_ensure()函數(shù)的定義,它是通過一個宏在相同的文件(hb-shape-plan.cc)中完成的:
#define HB_SHAPER_IMPLEMENT(shaper) \
HB_SHAPER_DATA_ENSURE_DECLARE(shaper, face) \
HB_SHAPER_DATA_ENSURE_DECLARE(shaper, font)
#include "hb-shaper-list.hh"
#undef HB_SHAPER_IMPLEMENT
然后,我們繼續(xù)追蹤,來看宏HB_SHAPER_DATA_ENSURE_DECLARE的定義(在hb-shaper-private.hh中):
#define HB_SHAPER_DATA_ENSURE_DECLARE(shaper, object) \
static inline bool \
hb_##shaper##_shaper_##object##_data_ensure (hb_##object##_t *object) \
{\
retry: \
HB_SHAPER_DATA_TYPE (shaper, object) *data = (HB_SHAPER_DATA_TYPE (shaper, object) *) hb_atomic_ptr_get (&HB_SHAPER_DATA (shaper, object)); \
if (unlikely (!data)) { \
data = HB_SHAPER_DATA_CREATE_FUNC (shaper, object) (object); \
if (unlikely (!data)) \
data = (HB_SHAPER_DATA_TYPE (shaper, object) *) HB_SHAPER_DATA_INVALID; \
if (!hb_atomic_ptr_cmpexch (&HB_SHAPER_DATA (shaper, object), NULL, data)) { \
HB_SHAPER_DATA_DESTROY_FUNC (shaper, object) (data); \
goto retry; \
} \
} \
return data != NULL && !HB_SHAPER_DATA_IS_INVALID (data); \
}
這么大一坨,都是些什么東西嘛。又是引用了一堆的宏,真是看得人暈死了。不用急,可以先逐個的看一下那些宏到底是怎么定義的,然后根據(jù)那些宏的定義,再逐行的解開這個函數(shù)就都大白了?
。首先是
HB_SHAPER_DATA,
HB_SHAPER_DATA_INSTANCE和
HB_SHAPER_DATA_TYPE的定義:
#define HB_SHAPER_DATA_TYPE(shaper, object) struct hb_##shaper##_shaper_##object##_data_t
#define HB_SHAPER_DATA_INSTANCE(shaper, object, instance) (* (HB_SHAPER_DATA_TYPE(shaper, object) **) &(instance)->shaper_data.shaper)
#define HB_SHAPER_DATA(shaper, object) HB_SHAPER_DATA_INSTANCE (shaper, object, object)
我們知道,用于實現(xiàn)hb_ot_shaper_face_data_ensure()這個函數(shù)時,HB_SHAPER_DATA_ENSURE_DECLARE(shaper, object)宏的shaper是“ot”,而object是“face”。先來解開如下的這一行:
HB_SHAPER_DATA_TYPE (shaper, object) *data = (HB_SHAPER_DATA_TYPE (shaper, object) *) hb_atomic_ptr_get (&HB_SHAPER_DATA (shaper, object));
可以看到這一行實際上是:
struct hb_ot_shaper_face_data_t *data = (struct hb_ot_shaper_face_data_t *) hb_atomic_ptr_get (&(*(struct hb_ot_shaper_face_data_t**)&face->shaper_data.ot));
到底是什么意思呢?說白了就是從face對象里面拿了一個數(shù)據(jù)成員出來,即face->shaper_data.ot。那它拿的那個成員到底又是怎么一回事呢?可以再來跟一下hb_face_t定義中與這個部分有關(guān)的一些內(nèi)容。來看一下那個shaper_data成員是個什么東西:
struct hb_face_t {
hb_object_header_t header;
ASSERT_POD ();
hb_bool_t immutable;
hb_reference_table_func_t reference_table_func;
void *user_data;
hb_destroy_func_t destroy;
unsigned int index;
mutable unsigned int upem;
mutable unsigned int num_glyphs;
struct hb_shaper_data_t shaper_data;
shaper_data的類型是hb_shaper_data_t,然后來看hb_shaper_data_t的定義:
struct hb_shaper_data_t {
#define HB_SHAPER_IMPLEMENT(shaper) void *shaper;
#include "hb-shaper-list.hh"
#undef HB_SHAPER_IMPLEMENT
};
聯(lián)系我們前面看到的hb-shaper-list.hh文件的內(nèi)容,可以發(fā)現(xiàn)hb_shaper_data_t就只是包含了一些void *類型的指針而已。 因而前面取出數(shù)據(jù)的那個過程,其實就是取出一個void *指針,然后做強制類型轉(zhuǎn)換。
讓我們回到HB_SHAPER_DATA_ENSURE_DECLARE的定義,接著來看HB_SHAPER_DATA_CREATE_FUNC宏的定義:
#define HB_SHAPER_DATA_CREATE_FUNC(shaper, object) _hb_##shaper##_shaper_##object##_data_create
展開這個宏就是:
_hb_ot_shaper_face_data_create
它其實是定義在hb-ot-shape.cc中的一個函數(shù):
hb_ot_shaper_face_data_t *
_hb_ot_shaper_face_data_create (hb_face_t *face)
{
return _hb_ot_layout_create (face);
}
至此,我們可以來總結(jié)一下hb_ot_shaper_face_data_ensure()函數(shù)所做的事情:它會從face對象里面拿到對應(yīng)于ot shaper的shaper_data,也就是一個struct hb_ot_shaper_face_data_t?對象,檢查一下是否為空;若為空,他就會去創(chuàng)建一個struct hb_ot_shaper_face_data_t?對象,并賦給face->shaper_data的ot成員face->shaper_data.ot。可以再多來看一點,那個struct hb_ot_shaper_face_data_t的實際類型是struct hb_ot_layout_t:
#define hb_ot_shaper_face_data_t hb_ot_layout_t
hb_ot_shaper_face_data_ensure()函數(shù)以什么為依據(jù)來判斷face所代表的字庫是ot shaper所能處理的字庫呢?就是看那個data對象是否能創(chuàng)建成功并且有效。
可見hb_ot_shaper_face_data_ensure()函數(shù)可能不僅僅是做check,它還可能新創(chuàng)建一個特定于shaper的結(jié)構(gòu),由face傳出,以供后面shaper的func在執(zhí)行時使用。
hb_shape_plan_create() --- 實際創(chuàng)建shape plan
來看hb_shape_plan_create_cached()中另外的一個重要函數(shù),也就是實際完成創(chuàng)建shape plan動作的hb_shape_plan_create()函數(shù),來看它的定義:
hb_shape_plan_t *
hb_shape_plan_create (hb_face_t *face,
const hb_segment_properties_t *props,
const hb_feature_t *user_features,
unsigned int num_user_features,
const char * const *shaper_list)
{
assert (props->direction != HB_DIRECTION_INVALID);
hb_shape_plan_t *shape_plan;
if (unlikely (!face))
face = hb_face_get_empty ();
if (unlikely (!props || hb_object_is_inert (face)))
return hb_shape_plan_get_empty ();
if (!(shape_plan = hb_object_create<hb_shape_plan_t> ()))
return hb_shape_plan_get_empty ();
hb_face_make_immutable (face);
shape_plan->default_shaper_list = shaper_list == NULL;
shape_plan->face = hb_face_reference (face);
shape_plan->props = *props;
hb_shape_plan_plan (shape_plan, user_features, num_user_features, shaper_list);
return shape_plan;
}
可以看到,這個函數(shù)主要是為hb_shape_plan_t對象分配了內(nèi)存空間,簡單地初始化了一些變量,完了之后便調(diào)用另外的一個函數(shù)hb_shape_plan_plan()來對hb_shape_plan_t對象做更細致的設(shè)置。來看hb_shape_plan_plan()的定義:
static void
hb_shape_plan_plan (hb_shape_plan_t *shape_plan,
const hb_feature_t *user_features,
unsigned int num_user_features,
const char * const *shaper_list)
{
const hb_shaper_pair_t *shapers = _hb_shapers_get ();
#define HB_SHAPER_PLAN(shaper) \
HB_STMT_START { \
if (hb_##shaper##_shaper_face_data_ensure (shape_plan->face)) { \
HB_SHAPER_DATA (shaper, shape_plan) = \
HB_SHAPER_DATA_CREATE_FUNC (shaper, shape_plan) (shape_plan, user_features, num_user_features); \
shape_plan->shaper_func = _hb_##shaper##_shape; \
shape_plan->shaper_name = #shaper; \
return; \
} \
} HB_STMT_END
if (likely (!shaper_list)) {
for (unsigned int i = 0; i < HB_SHAPERS_COUNT; i++)
if (0)
;
#define HB_SHAPER_IMPLEMENT(shaper) \
else if (shapers[i].func == _hb_##shaper##_shape) \
HB_SHAPER_PLAN (shaper);
#include "hb-shaper-list.hh"
#undef HB_SHAPER_IMPLEMENT
} else {
for (; *shaper_list; shaper_list++)
if (0)
;
#define HB_SHAPER_IMPLEMENT(shaper) \
else if (0 == strcmp (*shaper_list, #shaper)) \
HB_SHAPER_PLAN (shaper);
#include "hb-shaper-list.hh"
#undef HB_SHAPER_IMPLEMENT
}
#undef HB_SHAPER_PLAN
}
又是一大堆宏在這兒繞來繞去。不過沒關(guān)系,畢竟這個函數(shù)的結(jié)構(gòu)總體看起來還算清晰。定義宏,然后通過include?"hb-shaper-list.hh"文件而產(chǎn)生代碼的這種手法,在前面是已經(jīng)有見識過了,因而這個部分的邏輯應(yīng)該也還不難理解。這個函數(shù)首先是調(diào)用了_hb_shapers_get ()函數(shù)獲取到一個hb_shaper_pair_t的列表,然后分為shaper_list為空和非空兩種情況來處理。先來看一下hb_shaper_pair_t的定義:
struct hb_shaper_pair_t {
char name[16];
hb_shape_func_t *func;
};
這個結(jié)構(gòu)只有兩個成員,一個是shaper的name,另外一個就是shape func,一個函數(shù)指針,其他不需要做過多的解釋。
當shaper_list為空時,也是執(zhí)行這個函數(shù)最經(jīng)常出現(xiàn)的情況,在那個if block里面實現(xiàn),由里面的for循環(huán),不難看出這個函數(shù)是會從_hb_shapers_get()返回的shaper list中挑選一個。由if block里面的else-if語句,可以知道,for循環(huán)每遍歷到一個shaper,就總有一個else-if能與之匹配,所以else-if語句僅有的作用,就只是幫助它的HB_SHAPER_PLAN()來識別一個shaper而已。而選擇shaper的主要依據(jù),還得看HB_SHAPER_PLAN()宏,展開這個宏的定義,可以發(fā)現(xiàn),此處也一樣是調(diào)用hb_##shaper##_shaper_face_data_ensure()函數(shù)來對字庫文件做檢查,而這個函數(shù)算是我們的老朋友了,如前所述,它主要是創(chuàng)建一個face data,若創(chuàng)建成功,harfbuzz-ng就認為相應(yīng)的shaper是可用的。
選擇一個shaper的具體含義又是什么呢?可以看宏HB_SHAPER_PLAN()接下來的幾行,首先是,通過一個函數(shù)_hb_##shaper##_shaper_##object##_data_create(),創(chuàng)建一個shape_plan的data,并賦值給shape_plan->shaper_data.shaper。比如,對于ot shaper,就是調(diào)用_hb_ot_shaper_shape_plan_data_create()函數(shù),創(chuàng)建一個struct hb_ot_shaper_shape_plan_data_t對象,并賦值給shape_plan->shaper_data.ot,以返回給調(diào)用者。接下來便是設(shè)置shape_plan的shaper_func為對應(yīng)shaper的shaper_func。最后就是將shape_plan的shaper_name設(shè)置為對應(yīng)shaper的shaper_name。
那當shaper_list非空時,又是怎樣的一個執(zhí)行過程呢?有相應(yīng)的block里面的code來看,它與shaper_list為空時,有兩點區(qū)別,一是,它在調(diào)用者傳進來的shaper_list中來選擇;二是,它是通過shaper的shaper_name類識別一個shaper的,而不像前面的case,是通過shaper_func來識別一個shaper。其他則都完全一樣。
總結(jié)
此處我們來總結(jié)一下,harfbuzz-ng選擇一個shaper的過程。首先,harfbuzz-ng是通過調(diào)用shaper的hb_##shaper##_shaper_face_data_ensure()函數(shù)來確定那個shaper是否可用的,這個函數(shù)實際上算是在對字庫文件做檢查,它會創(chuàng)建一個face data,若創(chuàng)建成功,則認為相應(yīng)的shaper可用,否則,認為shaper不可用。這個函數(shù)還會將創(chuàng)建的face data賦值給face->shaper_data.shaper,以返回給調(diào)用者。確定了一個shaper可用之后,harfbuzz-ng還會通過調(diào)用shaper的_hb_##shaper##_shaper_shape_plan_data_create()函數(shù)創(chuàng)建一個shape plan的data,并通過shape_plan->shaper_data.shaper返回給調(diào)用者。然后就是為shape_plan設(shè)置適當?shù)膕haper_func和shaper_name,其中的shaper_func是名為_hb_##shaper##_shape的函數(shù)。另外,就是harfbuzz-ng在選擇shaper時是有按一定的優(yōu)先級的,在hb-shaper-list.hh文件中,被列出的越靠前的shaper,其優(yōu)先級就相應(yīng)的越高。