harfbuzz-ng如何选择一个shaper

harfbuzz-ng如何选择一个shaper

harfbuzz-ng在shape文字时,依然是会根据文字的特点,选中一个种shaper,然后再将实际shape的过程都交给shaper来处理这样。只是在harfbuzz-ng中,不像在老版harfbuzz中那样,是简单的依据所传入字串的script,通过一个表就直接地选定一个shaper,而是在参考字串的特性(script)之外,还会参考字库文件本身的特征,来最终选定一个shaper。同时,harfbuzz-ng中的shaper与老版harfbuzz中的shaper在概念上也有一定的区别。老版harfbuzz中的shaper基本上都是针对某种特定的语言而实现,并借助于内部的OpenType处理功能,来提供OpenType的高级渲染操作,可以称为是语言shaper吧。而在harfbuzz-ng中,其shaper则主要包括Graphite2 shaper,OpenType shaper这样的一些,可以称为是字库shaper吧。借助于harfbuzz-ng这样的一种结构,用户可以只为harfbuzz-ng编写一个客户端,然后简单的将Graphite2之类的其他shape engine接到harfbuzz-ng下面,以实现对字串的最优化shaping。harfbuzz-ng本身主要是实现了OpenType shaper,因而下面我们也会更多的关注与OpenType shaper有关的一些内容。下面我们就来看一下,在harfbuzz-ng中,选择shaper的逻辑是怎样的吧。当然,下面的code分析,一定是基于harfbuzz某个特定的版本的,这个版本实际上是0.9.10,harfbuzz目前都还依然处于开放状态中,本文的分析对于未来的某些版本也可能会有不适用的状况。

harfbuzz-ng shape文本的主流程

首先,来看一下harfbuzz-ng的主入口函数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);
}

这个函数接收font和buffer参数,其中font里面包含有与字体相关的信息,比如所使用的字库文件的内容,字体的大小等;而buffer中则包含有关于字串的信息,比如字串的内容,字串的方向和script等。而这个函数的 features  和 num_features 参数常常是NULL和0,因为客户端通常都不需要自己来确定shape一个字串所需要的features嘛,选择到底要使用那些features的工作,完全交给harfbuzz-ng来就行了。完成对字串的shaping之后,结果会通过传入的buffer参数返回给调用者。

可以看到hb_shape()的定义倒是简单的很,就只是调用了hb_shape_full()来完成所有的工作而已。接下来来看一下hb_shape_full()的实现:

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;
}

可以看到这个函数对字串做shape的过程:

  1. 补足buffer的一些信息。由guess_segment_properties()的实现,我们可以看到,补足的主要是script,direction和language的相关信息。也就是说,harfbuzz-ng客户端,其实可以不为buffer设置这些信息,然后在这个地方,harfbuzz-ng会自己来补足这些信息。
  2. 调用hb_shape_plan_create_cached()函数来创建shape plan。
  3. 调用hb_shape_plan_execute()函数来执行shape plan。
  4. 调用hb_shape_plan_destroy()函数来销毁shape plan。

不过所谓的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可以借助于这个对象来获取字库文件的一些内容,比如获取OpenType的表等;而hb_segment_properties_t则主要保存字串属性相关的一些信息,包括script,direction和language等。可以看到,这个结构前面的几个字段,主要是与字库文件(face)和字串属性(props)有关的一些内容,而后面的几个字段,则是与选中的shaper有关的一些内容。不难理解,创建shape plan的过程,大体上应该是,用client传入的参数,设置前面的几个字段(default_shaper_list,face和props),然后依据于client传入的参数,创建或者确定后面几个字段(shaper_func,shaper_name和shaper_data)的内容。接着,我们就来看一下,harfbuzz-ng到底是如何完成这一切的吧。

hb_shape_plan_create_cached() --- shape plan创建的主要流程

前面提到,hb_shape_plan_create_cached()函数创建shape plan,那么我们就先来看一下这个函数的定义

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);
}

可以看到,这个函数正是主要依据于faceprops来创建shape plan的。这个函数中为两种情况做了一些特殊的处理:第一种是num_user_features大于0的情况,即客户端指定了一些features;第二种是shaper_list非空的情况,即客户端已经提供了一个shaper列表给harfbuzz-ng来选则适当的shaper。

第一种情况简单明了,会直接调用hb_shape_plan_create()函数来创建shape plan,此处不再多说,后面会再来说明这个函数的定义。那就来看一下第二种情况的一些特殊处理:

  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_PLANHB_SHAPER_IMPLEMENT,然后外加一个什么也没做的空循环,在然后就是include了一个文件而已。难道include的那个文件暗藏玄机?没错,所有的秘密还确实都在那个文件里了。我们来看一下那个hb-shaper-list.hh的内容

#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. */

至此,那两个宏的作用看起来就清晰多了嘛。根据这个文件中的内容,将前面看到的那两个宏都解开来看那段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将宏解开的真实结果。由hb-shaper-list.hh的内容,我们知道,for循环下的else-if block的数量和内容,都会依赖于到底有多少种shaper是通过宏而被enabled起来的。不过,有两种shaper是必定会被enabled的,一种是harfbuzz-ng实现的ot,另外一种就是fallback。

由这段code来看,前面所提到的针对第二种情况的特殊处理,其实也就是补足proposalshape_func相关的信息,以便于后面在匹配shape plan时,能有更多的依据。

这段code会逐个的检查传进来的那个shaper_list中的shaper,以确定合适的shaper。它会调用shaper的hb_##shaper##_shaper_face_data_ensure()函数,比如hb_ot_shaper_face_data_ensure()等,来检查相应的shaper是否能够处理传入的那个face(字库文件),如果可以,则将相应的shaper_func函数赋给proposal.shaper_func

那么,在选择shaper的时候为什么会需要对字库做检查呢?因为确实有一些shaper对字库有特殊的要求,比如ot的shaper就要求传入的字库必须是一个OpenType字库,而不能是简单的TrueType字库,graphite2的shaper则对字库有更高的要求。

根据需要(shaper_list非空的那段code,通常都不会执行到,shaper_list为空的时候多),对第二种情况作了proposalshape_func相关信息的补充之后,hb_shape_plan_create_cached()函数就会从face对象中取出一个缓存的hb_face_t::plan_node_t链表(face->shape_plans),并检查是否能够找到proposal所描述的shape plan,若可以找到则将shape plan返回给调用者,创建shape plan的过程就算结束了。

hb_shape_plan_create_cached()函数没能在face的缓存中找到所需要的shape plan的话,则它就会调用hb_shape_plan_create()来创建一个,将这个shape plan缓存进face的shape_plans链表里去,并返回刚刚创建的这个shape plan。

hb_##shaper##_shaper_face_data_ensure (face)  --- 对字库文件做检查

那个所谓的对字库的检查到底是如何进行的呢?以ot的shaper为例,来看一下字库检查都做了些什么事情。来看hb_ot_shaper_face_data_ensure()函数的定义,它是通过一个宏在相同的文件(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

然后,我们继续追踪,来看宏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); \
}
这么大一坨,都是些什么东西嘛。又是引用了一堆的宏,真是看得人晕死了。不用急,可以先逐个的看一下那些宏到底是怎么定义的,然后根据那些宏的定义,再逐行的解开这个函数就都真相大白了  。首先是 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)

我们知道,用于实现hb_ot_shaper_face_data_ensure()这个函数时,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对象里面拿了一个数据成员出来,即face->shaper_data.ot。那它拿的那个成员到底又是怎么一回事呢?可以再来跟一下hb_face_t定义中与这个部分有关的一些内容。来看一下那个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
};

联系我们前面看到的hb-shaper-list.hh文件的内容,可以发现hb_shaper_data_t就只是包含了一些void *类型的指针而已。 因而前面取出数据的那个过程,其实就是取出一个void *指针,然后做强制类型转换。

让我们回到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中的一个函数:

hb_ot_shaper_face_data_t *
_hb_ot_shaper_face_data_create (hb_face_t *face)
{
  return _hb_ot_layout_create (face);
}

至此,我们可以来总结一下hb_ot_shaper_face_data_ensure()函数所做的事情:它会从face对象里面拿到对应于ot shaper的shaper_data,也就是一个struct hb_ot_shaper_face_data_t 对象,检查一下是否为空;若为空,他就会去创建一个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()函数以什么为依据来判断face所代表的字库是ot shaper所能处理的字库呢?就是看那个data对象是否能创建成功并且有效。

可见hb_ot_shaper_face_data_ensure()函数可能不仅仅是做check,它还可能新创建一个特定于shaper的结构,由face传出,以供后面shaper的func在执行时使用。

hb_shape_plan_create() --- 实际创建shape plan

来看hb_shape_plan_create_cached()中另外的一个重要函数,也就是实际完成创建shape plan动作的hb_shape_plan_create()函数,来看它的定义:

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;
}

可以看到,这个函数主要是为hb_shape_plan_t对象分配了内存空间,简单地初始化了一些变量,完了之后便调用另外的一个函数hb_shape_plan_plan()来对hb_shape_plan_t对象做更细致的设置。来看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
}

又是一大堆宏在这儿绕来绕去。不过没关系,毕竟这个函数的结构总体看起来还算清晰。定义宏,然后通过include "hb-shaper-list.hh"文件而产生代码的这种手法,在前面是已经有见识过了,因而这个部分的逻辑应该也还不难理解。这个函数首先是调用了_hb_shapers_get ()函数获取到一个hb_shaper_pair_t的列表,然后分为shaper_list为空和非空两种情况来处理。先来看一下hb_shaper_pair_t的定义:

struct hb_shaper_pair_t {
  char name[16];
  hb_shape_func_t *func;
};

这个结构只有两个成员,一个是shaper的name,另外一个就是shape func,一个函数指针,其他不需要做过多的解释。

shaper_list为空时,也是执行这个函数最经常出现的情况,在那个if block里面实现,由里面的for循环,不难看出这个函数是会从_hb_shapers_get()返回的shaper list中挑选一个。由if block里面的else-if语句,可以知道,for循环每遍历到一个shaper,就总有一个else-if能与之匹配,所以else-if语句仅有的作用,就只是帮助它的HB_SHAPER_PLAN()来识别一个shaper而已。而选择shaper的主要依据,还得看HB_SHAPER_PLAN()宏,展开这个宏的定义,可以发现,此处也一样是调用hb_##shaper##_shaper_face_data_ensure()函数来对字库文件做检查,而这个函数算是我们的老朋友了,如前所述,它主要是创建一个face data,若创建成功,harfbuzz-ng就认为相应的shaper是可用的。

选择一个shaper的具体含义又是什么呢?可以看宏HB_SHAPER_PLAN()接下来的几行,首先是,通过一个函数_hb_##shaper##_shaper_##object##_data_create(),创建一个shape_plan的data,并赋值给shape_plan->shaper_data.shaper。比如,对于ot shaper,就是调用_hb_ot_shaper_shape_plan_data_create()函数,创建一个struct hb_ot_shaper_shape_plan_data_t对象,并赋值给shape_plan->shaper_data.ot,以返回给调用者。接下来便是设置shape_plan的shaper_func为对应shaper的shaper_func。最后就是将shape_plan的shaper_name设置为对应shaper的shaper_name。

shaper_list非空时,又是怎样的一个执行过程呢?有相应的block里面的code来看,它与shaper_list为空时,有两点区别,一是,它在调用者传进来的shaper_list中来选择;二是,它是通过shaper的shaper_name类识别一个shaper的,而不像前面的case,是通过shaper_func来识别一个shaper。其他则都完全一样。

总结

此处我们来总结一下,harfbuzz-ng选择一个shaper的过程。首先,harfbuzz-ng是通过调用shaper的hb_##shaper##_shaper_face_data_ensure()函数来确定那个shaper是否可用的,这个函数实际上算是在对字库文件做检查,它会创建一个face data,若创建成功,则认为相应的shaper可用,否则,认为shaper不可用。这个函数还会将创建的face data赋值给face->shaper_data.shaper,以返回给调用者。确定了一个shaper可用之后,harfbuzz-ng还会通过调用shaper的_hb_##shaper##_shaper_shape_plan_data_create()函数创建一个shape plan的data,并通过shape_plan->shaper_data.shaper返回给调用者。然后就是为shape_plan设置适当的shaper_func和shaper_name,其中的shaper_func是名为_hb_##shaper##_shape的函数。另外,就是harfbuzz-ng在选择shaper时是有按一定的优先级的,在hb-shaper-list.hh文件中,被列出的越靠前的shaper,其优先级就相应的越高

你可能感兴趣的:(harfbuzz,harfbuzz-ng,shaping,OpenType)