本章主要介绍harfbuzz,并结合freetype、truetype来写一个代码示例,该示例可以在qt的窗口上显示出来,你将学习到的知识点有:
- harfbuzz是什么,用来做什么的
- harfbuzz如何配合freetype、truetype使用
harfbuzz的重要的一个功能是根据某种语言的字符组合规则来进行字形变换。下图是高棉语 “បាទ”(中文意思:是的),在是否使用harfbuzz整形的情况下的两种字符显示情况。
未使用harfbuzz
使用harfbuzz
高棉语中还有很多这样的变换规则,是它本身这种语言觉得的,harfbuzz能够解析这种规则,然后是字符正确显示
harfbuzz、freetype、truetype联合应用示例
一、 什么是harfbuzz,作用是什么
相关网站
(https://www.freedesktop.org/wiki/Software/HarfBuzz/)
https://zh.wikipedia.org/wiki/HarfBuzz
harfbuzz是一个开源的文本整形引擎,网上说它是属于OpenType的一部分。由于OpenType是FreeType的一个升级版本,所以它也可以和FreeType一起使用。
harfbuzz广泛使用在Firefox,GNOME,ChromeOS,Chrome,LibreOffice中,Qt底层的文字引擎也使用了harfbuzz
什么是整形引擎?
笔者的理解就是:当两个或多个单独的字符组合到一起的时候,可能就变成了另外的字符。这在某些语言和艺术字方面经常用到,列如:“f"和"i"这两个字符组合成"fi”,这在有些语言或者艺术字里面可能要写成:
这应该是它的一个最基本的作用。
二、harfbuzz代码示例
下面笔者将以高棉语truetype字体文件来介绍harfbuzz的基本用法。
准备材料
- truetype字体文件KhmerUI.ttf,这是高棉语的字体文件
- freetype库,这里使用的是freetype-2.10.1
- harfbuzz库,这里使用的是harfbuzz-2.6.4
- ubuntu16.04,我是虚拟机里面运行的
- 安装Qt Creator
以上材料都可以在 这里下载
开始写代码
使用Qt Creator作为IDE
工程目录结构如下:
.
├── contrib
│ ├── freetype2 ----- freetype2 头文件
│ └── harfbuzz ------harfbuzz头文件
├── encoding_conv.c -----utf8转换成ucs2算法
├── encoding_conv.h
├── font.qrc
├── fonts
│ └── KhmerUI.ttf ------高棉语字体文件
├── lib
│ ├── freetype-2.10.1 -------freetype共享库
│ └── harfbuzz-2.6.4 -------harfbuzz共享库
├── main.cpp
├── mainwin.cpp
├── mainwin.h
├── qt_font_freetype.pro Qt工程文件
└── qt_font_freetype.pro.user
主要代码分析
//MainWin构造函数
MainWin::MainWin(QWidget *parent)
: QWidget(parent)
{
simulator_screen_width = 800;
simulator_screen_height = 300;
resize(QSize(simulator_screen_width, simulator_screen_height));
pixel_mode = 4;
simulator_screen = (unsigned int *)qMallocAligned(simulator_screen_width*simulator_screen_height*pixel_mode, 1);
if (simulator_screen == Q_NULLPTR)
{
qDebug("simulator_screen malloc failed\n");
}
set_simulator_screen_color(WHITE_COLOR);
// "បាទ"在高棉语中表示"yes"
draw_text(40, 40, "Khmer use harfbuzz:", BLUE_COLOR, 27);
draw_text(450, 40, "បាទ", BLUE_COLOR, 27);
}
draw_text函数,最重要的函数
qint32 MainWin::draw_text(int x, int y, char *utf8_text, unsigned int color, int font_size)
{
FILE *fpttf;
unsigned int fileLen = 0;
char *fontData = NULL;
void *userData = NULL;
unsigned int upem = 0;
unsigned int count = 0;
int pen_x = x;
int pen_y = y;
int abjust_x = 0;
int abjust_y = 0;
int a,b,i,j;
int width;
unsigned int draw_color;
int m_font_size;
int loop = 0;
int error = 0;
hb_glyph_info_t *info;
hb_glyph_info_t *infos = NULL;
hb_blob_t *blob = NULL;
hb_destroy_func_t destroy;
hb_face_t *face;
hb_font_t *font;
hb_memory_mode_t mm = HB_MEMORY_MODE_WRITABLE;
FT_Face FTFace;
hb_buffer_t *buffer;
fpttf = fopen(ttf_file, "rb");
if (NULL == fpttf)
{
qDebug("open ttf file faild!, [%s, %d]\r\n", __FUNCTION__, __LINE__);
return 0;
}
fseek(fpttf, 0, SEEK_END);
fileLen = ftell(fpttf);
fseek(fpttf, 0, SEEK_SET);
fontData = (char *)malloc(fileLen);
destroy = free;
fileLen = fread(fontData, 1, fileLen, fpttf);
fclose(fpttf);
userData = (void *)fontData;
blob = hb_blob_create((const char *)fontData, fileLen, mm, userData, destroy);
face = hb_face_create(blob, 0);
hb_blob_destroy(blob);
blob = NULL;
upem = hb_face_get_upem(face);
font = hb_font_create(face);
hb_font_set_scale(font, upem, upem);
hb_ft_font_set_funcs(font);
// init freetype
FT_Library library;
if (font_size < 1 || font_size > 72)
{
m_font_size = 18;
}
if (FT_Init_FreeType(&library) != 0)
{
return -2;
}
if (FT_New_Face(library, ttf_file, 0, &FTFace) != 0)
{
qDebug("func:%s line:%d \r\n",__FUNCTION__,__LINE__);
return -3;
}
FT_Set_Char_Size(FTFace, 0, font_size * 64, 96, 96);
buffer = hb_buffer_create();
hb_buffer_add_utf8(buffer, (const char *)utf8_text, -1, 0, -1);
hb_buffer_guess_segment_properties(buffer);
if(font!=NULL)
{
hb_shape(font, buffer, NULL, 0);
count = hb_buffer_get_length(buffer);
infos = hb_buffer_get_glyph_infos(buffer, NULL);
}
for (loop = 0; loop < count; loop++)
{
info = &infos[loop];
error = FT_Load_Glyph(FTFace, info->codepoint, FT_LOAD_DEFAULT);
if (FTFace->glyph->format != FT_GLYPH_FORMAT_BITMAP)
{
error = FT_Render_Glyph(FTFace->glyph, ft_render_mode_normal);
}
FT_GlyphSlot slot = FTFace->glyph;
FT_Bitmap bitmap = slot->bitmap;
if(error==0)
{
abjust_x = slot->bitmap_left;
abjust_y = (FTFace->size->metrics.ascender >> 6) - slot->bitmap_top;
for (a = 0, j = pen_y + abjust_y; j < simulator_screen_height && a < bitmap.rows; j++, a++)
{
for (b = 0, i = pen_x + abjust_x; i < simulator_screen_width && b < bitmap.width; i++, b++)
{
draw_color = (color & 0x00ffffff) | (bitmap.buffer[ a*bitmap.width + b ] << 24);
simulator_screen[j*simulator_screen_width + i] = draw_color;
}
}
width += slot->advance.x >> 6;
pen_x += slot->advance.x >> 6;
}
}
this->repaint();
return width;
}
draw_text函数中,freetype是使用方式和前一篇博客基本一样,只不过在只用freetype渲染字符前,先使用harfbuzz整一下形。进一步来说,高棉语中的truetype文件包含了全部的高棉语字符,也就是包含了整形后的字符,在使用freetype渲染字符前,先用harfbuzz获取相邻两个或者多个字符组合成新字符的信息,这个过程好像重定位,最后把获取的重定位信息传给freetype就可以正确渲染出字符了。
高棉语truetype文件-----》harfbuzz整形------》freetype渲染
三、总结
- 不使用harfbuzz,单单用freetype+truetype文件也可以渲染出高棉语的字符串,但是可能字符串的意思不正确。harfbuzz可以根据某种语言或者某种艺术字的特性,重新整形,得到正确的字符串。在此过程中,harfbuzz就像一个中间件。
- 到此为止,关于truetype、freetype和harfbuzz的介绍已经应用示例都写完了,这都是笔者在工作过程中的一些技术总结。笔者在博客中贴出的是主要代码,如果真的想要对这部分知识有一个更深入了解,还是建议下载笔者上传的资源文件。
陌上人如玉,君子世无双