[Harfbuzz](http://harfbuzz.org/ 是一個 OpenType 文本整形引擎陷遮。當前的 Harfbuzz 代碼庫寝蹈,之前被稱為 harfbuzz-ng慧域,版本號為 1.x.x罗售,它是穩(wěn)定的且處于活躍的維護之中辜窑。Harfbuzz 的使用非常廣泛,在最新版本的 Firefox寨躁,GNOME穆碎,ChromeOS,Chrome职恳,LibreOffice所禀,XeTeX方面,Android,和 KDE 等項目中都有應用色徘。Harfbuzz 的代碼可以在 這里 下載恭金,也可以通過 GitHub 訪問。
老的 HarfBuzz 代碼庫褂策,現(xiàn)在被稱為 harfbuzz-old横腿,它從 FreeType,Pango斤寂,和 Qt 派上而來耿焊,可以在 這里 下載。老的 HarfBuzz 代碼庫目前已經(jīng)不再維護了遍搞。
Harfbuzz 在代碼結構上罗侯,與 harfbuzz-old 的差別非常大。前面 Behdad Esfahbod 發(fā)的那篇名為 Harfbuzz API 設計(Harfbuzz API design) 的郵件溪猿,所描述的是新 Harfbuzz API 的設計钩杰。
本文來看一下 Harfbuzz API 的基本用法。學習一個開源庫的 API 的用法最方便的途徑诊县,常常正是庫本身包含的一些示例程序或這測試程序讲弄。下載當前最新的發(fā)布版本 1.7.5 的源碼。將源碼解壓縮之后依痊,通過如下命令編譯它:
harfbuzz-1.7.5$ ./configure
harfbuzz-1.7.5$ make
編譯過程將產(chǎn)生 Harfbuzz 的二進制庫文件垂睬,和一些測試程序的可執(zhí)行文件,位于 harfbuzz-1.7.5/src/test.cc
的即是其中一個測試程序抗悍。這個測試程序編譯之后產(chǎn)生的可執(zhí)行文件為 harfbuzz-1.7.5/src/test
驹饺,通過如下方式執(zhí)行:
harfbuzz-1.7.5$ src/test font-file.ttf
即需要唯一的參數(shù),字庫文件的路徑缴渊。我們對這個測試程序做一點簡單的修改赏壹,讓它處理泰語和緬甸語,字庫文件我們使用 Android 7.1.1 代碼庫中衔沼,external/noto-fonts/other/
下的 NotoSansThai-Regular.ttf
和 NotoSansMyanmar-Regular.ttf
:
harfbuzz-1.7.5$ src/test ~/Androids/android_7.1.1/external/noto-fonts/other/NotoSansThai-Regular.ttf
Opened font file ~/Androids/android_7.1.1/external/noto-fonts/other/NotoSansThai-Regular.ttf: 21380 bytes long
cluster 0 glyph 0x4 at (0,0)+(1264,0)
cluster 0 glyph 0x4e at (7,0)+(0,0)
cluster 0 glyph 0x5e at (127,0)+(0,0)
cluster 0 glyph 0x37 at (0,0)+(994,0)
cluster 3 glyph 0x25 at (0,0)+(1315,0)
cluster 4 glyph 0x26 at (0,0)+(1225,0)
cluster 5 glyph 0x27 at (0,0)+(1301,0)
cluster 6 glyph 0x30 at (0,0)+(1320,0)
cluster 7 glyph 0x31 at (0,0)+(1379,0)
cluster 8 glyph 0x33 at (0,0)+(1195,0)
harfbuzz-1.7.5$ src/test ~/Androids/android_7.1.1/external/noto-fonts/other/NotoSansMyanmar-Regular.ttf
Opened font file ~/Androids/android_7.1.1/external/noto-fonts/other/NotoSansMyanmar-Regular.ttf: 108160 bytes long
cluster 0 glyph 0x9 at (0,0)+(1381,0)
cluster 0 glyph 0x196 at (0,0)+(993,0)
cluster 2 glyph 0x5 at (0,0)+(1384,0)
cluster 2 glyph 0x195 at (0,0)+(547,0)
cluster 4 glyph 0x22 at (0,0)+(2308,0)
cluster 4 glyph 0x197 at (-42,0)+(0,0)
cluster 6 glyph 0x4 at (0,0)+(2302,0)
cluster 6 glyph 0xd5 at (-19,-50)+(0,0)
cluster 6 glyph 0x196 at (0,0)+(993,0)
cluster 10 glyph 0x22 at (0,0)+(2308,0)
cluster 11 glyph 0x16 at (0,0)+(1311,0)
cluster 11 glyph 0x104 at (-4,-50)+(0,0)
cluster 11 glyph 0x195 at (0,0)+(547,0)
cluster 15 glyph 0xe at (0,0)+(2271,0)
cluster 15 glyph 0x197 at (-14,0)+(0,0)
cluster 15 glyph 0xd2 at (0,0)+(534,0)
cluster 18 glyph 0x15 at (0,0)+(2304,0)
cluster 18 glyph 0x19f at (-57,0)+(0,0)
cluster 18 glyph 0xd1 at (-31,0)+(0,0)
cluster 18 glyph 0x1a1 at (0,0)+(709,0)
harfbuzz-1.7.5/src/test.cc
的完整源碼如下:
#include "hb-private.hh"
#include "hb.h"
#ifdef HAVE_GLIB
# include <glib.h>
# if !GLIB_CHECK_VERSION (2, 22, 0)
# define g_mapped_file_unref g_mapped_file_free
# endif
#endif
#include <stdlib.h>
#include <stdio.h>
#ifdef HAVE_FREETYPE
#include "hb-ft.h"
#endif
int
main (int argc, char **argv)
{
hb_blob_t *blob = nullptr;
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, nullptr);
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 = nullptr;
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);
#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 = myanmarChars;
// 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_buffer_guess_segment_properties (buffer);
hb_shape (font, buffer, nullptr, 0);
unsigned int count = hb_buffer_get_length (buffer);
hb_glyph_info_t *infos = hb_buffer_get_glyph_infos (buffer, nullptr);
hb_glyph_position_t *positions = hb_buffer_get_glyph_positions (buffer, nullptr);
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 (%d,%d)+(%d,%d)\n",
info->cluster,
info->codepoint,
pos->x_offset,
pos->y_offset,
pos->x_advance,
pos->y_advance);
}
hb_buffer_destroy (buffer);
hb_font_destroy (font);
hb_face_destroy (face);
return 0;
}
接著再來看前面那段code的結構蝌借,上面這段代碼執(zhí)行的步驟如下:
- 讀取字庫文件中的數(shù)據(jù),然后創(chuàng)建
hb_blot_t
指蚁。
- 讀取字庫文件中的數(shù)據(jù),然后創(chuàng)建
- 利用前面創(chuàng)建的那個含有字庫文件數(shù)據(jù)的
blob
菩佑,創(chuàng)建一個face
。
- 利用前面創(chuàng)建的那個含有字庫文件數(shù)據(jù)的
- 利用前面創(chuàng)建的
face
凝化,創(chuàng)建一個font
稍坯。然后把字體大小的信息(ppem)及字體設計空間向用戶空間轉(zhuǎn)換的系數(shù)(scale)設置給font
。計算ppem
及scale
的那段代碼借用了android 4.2TextLayoutCache.cpp
的一些做法。
- 利用前面創(chuàng)建的
- 創(chuàng)建一個
buffer
瞧哟,把文本添加進去混巧。這個地方用UTF-16
編碼,是因為就手動編碼 Unicode 而言勤揩,對于許多復雜語系的 Unicode 范圍咧党,UTF-16 比 UTF-8 要方便的多,因而也使我們可以更方便地修改它陨亡。
- 創(chuàng)建一個
- 調(diào)用 Harfbuzz 的主
shape
接口執(zhí)行shape
動作傍衡。
- 調(diào)用 Harfbuzz 的主
- 最后從
shape
之后的buffer
中,取出glyph
和position
相關信息负蠕。
- 最后從
通常情況下對于 Harfbuzz API 的使用聪舒,大體上如上面所述。用一張圖來簡單說明上面的過程:
這樣的用法虐急,之所以稱為基本用法,有如下這樣一些原因:
- 前面的第 2滔迈、3 步中止吁,在創(chuàng)建
face
和font
時,是直接通過字庫文件的路徑進行的燎悍。通常情況每個系統(tǒng)都會有自己的字庫文件管理系統(tǒng)和 Glyph 管理系統(tǒng)敬惦,這種做法就完全沒有考慮與現(xiàn)有系統(tǒng)的這些模塊銜接的問題。在實際系統(tǒng)中谈山,這兩個對象應該通過相應有 callback 參數(shù)的那些接口來創(chuàng)建俄删。 - 在 Harfbuzz API Design 中,我們看到有提到 Unicode callback 及 Script奏路、Language 和 Direction 這些文本屬性等畴椰,這些都是需要正確的設置給
buffer
的,因而前面第 4 步所對應的這個測試程序的做法鸽粉,所創(chuàng)建的buffer
是不夠完整的斜脂。 - 在打印位置信息時,我們看到有通過
HBFixedToFloat()
這個函數(shù)來對 Harfbuzz 輸出的位置信息做一個轉(zhuǎn)換触机,轉(zhuǎn)換為float
格式的像素個數(shù)值帚戳。可以看到這個地方除了一個2048
儡首。這個系數(shù)在這個測試程序里用的是一個猜想的值片任。字體大小為36
,所以猜想返回的advance
值應該處于這一數(shù)量級蔬胯。所以取了2048
這個系數(shù)对供。
Done.