Harfbuzz

Stella981
• 阅读 923

Harfbuzz是一个OpenType 的shaping engine (http://www.freedesktop.org/wiki/Software/HarfBuzz)。目前Harfbuzz有两个Code trees,早期的那个code tree称之为Harfbuzz,或者old harfbuzz。而目前还处于比较活跃的开发状态的这个code tree,在整个代码的结构上面,与前面的那一版,有着非常大的区别,为了能够区分这两者,新的这个称之为harfbuzz-ng。前面Behdad Esfahbod所发的那个Title为Harfbuzz API 设计(Harfbuzz API design)的mail,所描述的是Harfbuzz-ng API的设计。

下面这段code来源于harfbuzz-0.9.10代码库(harfbuzz-0.9.10/src/test.cc):

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "hb.h"

#ifdef HAVE_GLIB
#include <glib.h>
#endif
#include <stdlib.h>
#include <stdio.h>

#ifdef HAVE_FREETYPE
#include "hb-ft.h"
#endif

static inline float HBFixedToFloat(int v) {
    // Harfbuzz uses 26.6 fixed point values for pixel offsets
    return v * (1.0f / 2048);
}

int main(int argc, char **argv) {
    hb_blob_t *blob = NULL;

    if (argc != 2) {
        fprintf(stderr, "usage: %s font-file.ttf\n", argv[0]);
        exit(1);
    }

    /* Create the blob */
    {
        const char *font_data;
        unsigned int len;
        hb_destroy_func_t destroy;
        void *user_data;
        hb_memory_mode_t mm;

#ifdef HAVE_GLIB
        GMappedFile *mf = g_mapped_file_new (argv[1], false, NULL);
        font_data = g_mapped_file_get_contents (mf);
        len = g_mapped_file_get_length (mf);
        destroy = (hb_destroy_func_t) g_mapped_file_unref;
        user_data = (void *) mf;
        mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE;
#else
        FILE *f = fopen(argv[1], "rb");
        fseek(f, 0, SEEK_END);
        len = ftell(f);
        fseek(f, 0, SEEK_SET);
        font_data = (const char *) malloc(len);
        if (!font_data)
            len = 0;
        len = fread((char *) font_data, 1, len, f);
        destroy = free;
        user_data = (void *) font_data;
        fclose(f);
        mm = HB_MEMORY_MODE_WRITABLE;
#endif

        blob = hb_blob_create(font_data, len, mm, user_data, destroy);
    }

    printf("Opened font file %s: %u bytes long\n", argv[1],
            hb_blob_get_length(blob));

    /* Create the face */
    hb_face_t *face = hb_face_create(blob, 0 /* first face */);
    hb_blob_destroy(blob);
    blob = NULL;
    unsigned int upem = hb_face_get_upem(face);

    int textSize = 36;
    uint16_t x_ppem, y_ppem;
    int x_scale, y_scale;

    x_ppem = y_ppem = textSize;
    const int kDevicePixelFraction = 64;
    const int kMultiplyFor16Dot16 = 1 << 16;
    float emScale = kDevicePixelFraction * kMultiplyFor16Dot16 / (float)upem;
    x_scale = emScale * textSize;
    y_scale = emScale * textSize;

    hb_font_t *font = hb_font_create(face);
    hb_font_set_scale(font, x_scale, y_scale);
    hb_font_set_ppem(font, x_ppem, y_ppem);
    printf("x_scale = %d, y_scale = %d, x_ppem = %d, y_ppem = %d\n", x_scale, y_scale, x_ppem, y_ppem);


#ifdef HAVE_FREETYPE
    hb_ft_font_set_funcs (font);
#endif

    hb_buffer_t *buffer = hb_buffer_create();

    uint16_t myanmarChars[] = {0x1005, 0x102C, 0x1001, 0x102B,
            0x101E, 0x102D, 0x1000, 0x1039, 0x1001, 0x102C,
            0x101E, 0x1012, 0x1039, 0x1013, 0x102B,
            0x100A, 0x102D, 0x102F, 0x1011, 0x102F, 0x1036, 0x1038
    };

    uint16_t thaiChars[] = {
            0xE01, 0xE49, 0xE33, 0xE20, 0xE21, 0xE22, 0xE2B, 0xE2C, 0xE2E

    };

    uint16_t *chars = thaiChars;

//    hb_buffer_add_utf8(buffer,
//            "\xe0\xa4\x95\xe0\xa5\x8d\xe0\xa4\xb0\xe0\xa5\x8d\xe0\xa4\x95", -1,
//            0, -1);
    hb_buffer_add_utf16(buffer, chars, -1, 0, -1);

    hb_shape(font, buffer, NULL, 0);

    unsigned int count = hb_buffer_get_length(buffer);
    hb_glyph_info_t *infos = hb_buffer_get_glyph_infos(buffer, NULL);
    hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer,
            NULL);

    for (unsigned int i = 0; i < count; i++) {
        hb_glyph_info_t *info = &infos[i];
        hb_glyph_position_t *pos = &positions[i];

        printf("cluster %d    glyph 0x%x at    offset(%f,%f)+position(%f,%f)\n",
                info->cluster,
                info->codepoint,
                HBFixedToFloat(pos->x_offset),
                HBFixedToFloat(pos->x_offset),
                HBFixedToFloat(pos->x_advance),
                HBFixedToFloat(pos->y_advance));
    }

    hb_buffer_destroy(buffer);
    hb_font_destroy(font);
    hb_face_destroy(face);

    return 0;
}

Harfbuzz-ng的编译,与网络上那些基于源码发布的项目的编译相比,也没有什么特别的地方,也都是configure->make这种。编译Harfbuzz-ng时,这个test app会一并被编译。可以像下面这样来执行这个test app:

./test ~/Data/font_language/font_files/android_4.2_fonts/DroidSansThai.ttf

这个地方的这个字库文件取自于android 4.2的codebase。android 4.2 codebase的下载可以参考Google官网上的说明。下面是执行的输出:

Opened font file ~/Data/font_language/font_files/android_4.2_fonts/DroidSansThai.ttf: 35584 bytes long
x_scale = 73728, y_scale = 73728, x_ppem = 36, y_ppem = 36
cluster 0    glyph 0x4 at    offset(0.000000,0.000000)+position(22.218750,0.000000)
cluster 0    glyph 0x4e at    offset(0.123047,0.123047)+position(0.000000,0.000000)
cluster 0    glyph 0x5e at    offset(2.232422,2.232422)+position(0.000000,0.000000)
cluster 0    glyph 0x37 at    offset(0.000000,0.000000)+position(17.472656,0.000000)
cluster 3    glyph 0x25 at    offset(0.000000,0.000000)+position(23.115234,0.000000)
cluster 4    glyph 0x26 at    offset(0.000000,0.000000)+position(21.533203,0.000000)
cluster 5    glyph 0x27 at    offset(0.000000,0.000000)+position(22.869141,0.000000)
cluster 6    glyph 0x30 at    offset(0.000000,0.000000)+position(23.203125,0.000000)
cluster 7    glyph 0x31 at    offset(0.000000,0.000000)+position(24.240234,0.000000)
cluster 8    glyph 0x33 at    offset(0.000000,0.000000)+position(21.005859,0.000000)

接着再来看前面那段code的结构。

  1. 从line 30 ~ line 61这31行,可以看到所做的事情就是读取字库文件中的数据,然后创建一个hb_blot_t。
  2. 从line 66 ~ line 70这几行,可以看到所做的事情是,利用前面创建的那个含有字库文件数据的blob,创建一个face。
  3. 从line 72 ~ line 86这几行,可以看到所做的事情主要是,利用前面创建的face,创建一个font。然后把字体大小的信息(ppem)及字体设计空间向用户空间转换的系数(scale)设置给font。计算ppem及scale 的那段code是借用了android 4.2 TextLayoutCache.cpp的一些code。
  4. 从line 93 ~ line 111这几行,所做的事情是,创建一个buffer,然后把text添加进去。这个地方用UTF-16编码,是因为就手动编码Unicode而言,对于许多复杂语系的Unicode范围,UTF-16比UTF-8要方便的多,因而也使得我们可以更为方便的来修改这个test。
  5. line 113是调用Harfbuzz-ng 的主shape接口来做shape动作。
  6. line 115 ~ line 131则是从shape之后的buffer中,取出glyph 和position的相关信息。

通常情况下对于Harfbuzz-ng API 的使用,大体上也会如上面的几个步骤。用一张图来说命上面的那个过程:

Harfbuzz

这样的用法,之所以称为basic usage,最起码有如下的这样一些原因:

  1. 前面的第2、3步,在创建face和font时,是直接通过字库文件。通常情况每个系统中都会有自己的字库文件管理系统和Glyph管理系统,这种做法就完全没有考虑与现有系统的这些模块衔接的问题。在实际系统中,着两个object应该要通过相应有callback参数的那些接口来创建。
  2. 在Harfbuzz API Design中,我们看到有提到Unicode callback及Script、Language和Direction这些text 属性等,这些都是需要正确的设置给buffer的,因而前面第4步所对应的这个test app的做法,所创建的buffer是不足够完整的。
  3. 在print position信息时,我们看到有通过HBFixedToFloat() 这个函数来对harfbuzz-ng 输出的position信息做一个转换,转换为float格式的像素个数值。可以看到这个地方是除了一个2048。这个系数在这个test app里面用的是一个猜想的值。字体大小为36,所以猜想返回的advance值应该要处于这一数量级。所以取用了2048这个系数。
点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Easter79 Easter79
3年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这