这是“FreeType2 教程”的第一部分。它将教会你如何
* 初始化库
* 通过创建一个新的 face 对象来打开一个字体文件
* 以点或者象素的形式选择一个字符大小
* 装载一个字形(glyph)图像,并把它转换为位图
* 渲染一个简单的字符串
* 容易地渲染一个旋转的字符串
下面的内容是编译一个使用了FreeType2库的应用程序所需要的指令。请谨慎阅读,自最近一次版本更新后我们已经更改了少许东西。
a.FreeType2 include 目录
你必须把FreeType2头文件的目录添加到编译包含(include)目录中。
注意,现在在Unix系统,你可以运行freetype-config脚本加上–cflags选项来获得正确的编译标记。这个脚本也可以用来检查安装在你 系统中的库的版本,以及需要的库和连接标记。
b. 包含名为ft2build.h的文件
Ft2build.h包含了接下来要#include的公共FreeType2头文件的宏声明。
c. 包含主要的FreeType2 API头文件
你要使用FT_FREETYPE_H宏来完成这个工作,就像下面这样:
#include <ft2build.h>
#include FT_FREETYPE_H
FT_FREETYPE_H是在ftheader.h中定义的一个特别的宏。Ftheader.h包含了一些安装所特定的宏,这些宏指名了 FreeType2 API的其他公共头文件。
你可以阅读“FreeType 2 API参考”的这个部分来获得头文件的完整列表。
#include语句中宏的用法是服从ANSI的。这有几个原因:
* 这可以避免一些令人痛苦的与FreeType 1.x公共头文件的冲突。
* 宏名字不受限于DOS的8.3文件命名限制。象FT_MULTIPLE_MASTERS_H或FT_SFNT_NAMES_H这样的名字比真实的文件名 ftmm.h和fsnames.h更具可读性并且更容易理解。* 它允许特别的安装技巧,我们不在这里讨论它。
注意:从FreeType 2.1.6开始,旧式的头文件包含模式将不会再被支持。这意味着现在如果你做了象下面那样的事情,你将得到一个错误:
#include <freetype/freetype.h>
#include <freetype/ftglyph.h>
. . .
简单地创建一个FT_Library类型的变量,例如library,然后象下面那样调用函数FT_Init_FreeType:
#include <ft2build.h>
#include FT_FREETYPE_H
FT_LIBRARY library;
...
Error = FT_Init_FreeType ( &library );
If ( error )
{
... 当初始化库时发生了一个错误 . . .
}
这个函数负责下面的事情:
* 它创建一个FreeType 2库的新实例,并且设置句柄library指向他。
* 它装载库中FreeType所知道的每一个模块。在这些模块支持下,你新建的library对象可以优雅地处理TrueType, Type 1, CID-keyed 和OpenType/CFF字体。
就像你所看到的,这个函数返回一个错误代码,如同FreeType API的大部分其他函数一样。值为0的错误代码始终意味着操作成功了,否则,返回值指示错误,library设为NULL。
a.从一个字体文件装载
应用程序通过调用FT_New_Face创建一个新的face对象。一个face对象描述了一个特定的字样和风格。例如,’Times New Roman Regular’和’Times New Roman Italic’对应两个不同的face。
FT_Library library; /* 库的句柄 */
FT_Face face; /* face对象的句柄 */
error = FT_Init_FreeType( &library );
if ( error ) { ... }
error = FT_New_Face( library,
"/usr/share/fonts/truetype/arial.ttf ",
0,
&face );
if ( error == FT_Err_Unknown_File_Format )
{
... 可以打开和读这个文件,但不支持它的字体格式
}
else if ( error )
{
... 其它的错误码意味着这个字体文件不能打开和读,
... 或者简单的说它损坏了...
}
Library
一个FreeType库实例的句柄,face对象从中建立。
Filepathname字体文件路径名(一个标准的C字符串)
Face_index
某些字体格式允许把几个字体face嵌入到同一个文件中。
这个索引指示你想装载的face。
如果这个值太大,函数将会返回一个错误。Index 0总是正确的。
Face
一个指向新建的face对象的指针。
当失败时其值被置为NULL。
FT_Library library; /* 库的句柄 */
FT_Face face; /* face对象的句柄 */
error = FT_Init_FreeType( &library );
if ( error ) { ... }
error = FT_New_Memory_Face( library,
buffer, /* 缓存的第一个字节 */
size, /* 缓存的大小(以字节表示) */
0, /* face索引 */
&face );
if ( error ) { ... }
Num_glyphs
这个值给出了该字体face中可用的字形(glyphs)数目。简单来说,一个字形就是一个字符图像。但一个字形不一定和字符代码一一对应。Flags
一个32位整数,包含一些用来描述face特性的位标记。例如,标记FT_FACE_FLAG_SCALABLE用来指示该face的字体格式是可伸缩并 且该字形图像可以渲染到任何字符象素尺寸。要了解face标记的更多信息,请阅读“FreeType 2 API 参考”。
Units_per_EM
这个字段只对可伸缩字体格式有效,在其他格式它将会置为0。它指示了EM所覆盖的字体单位的个数。
Num_fixed_size
这个字段给出了当前face中嵌入的位图的个数。简单来说,一个strike就是某一特定字符象素尺寸下的一系列字形图像。例如,一个字体face可以包 含象素尺寸为10、12和14的strike。要注意的是即使是可伸缩的字体格式野可以包含嵌入的位图!
Fixed_sizes
一个指向FT_Bitmap_Size成员组成的数组的指针。每一个FT_Bitmap_Size指示face中的每一个strike的水平和垂直字符象 素尺寸。
当FT_New_Face或其他创建face函数调用时,它会自动在face中新建一个size对象,并返回。该size对象可以通过 face->size直接访问。
注意:一个face对象可以同时处理一个或多个size对象,但只有很少程序员需要用到这个功能,因而,我们决定简化该API,(例如,每个face对象 只拥有一个size对象)。但是我们仍然通过提供附加的函数来访问多个Size对象。
当一个新的face对象建立时,对于可伸缩字体格式,size对象默认值为字符大小水平和垂直均为10象素。对于定长字体格式,这个大小是未定义的,这就 是你必须在装载一个字形前设置该值的原因。
使用FT_Set_Char_Size完成该功能。这里有一个例子,它在一个300×300dpi设备上把字符大小设置为16pt。
error = FT_Set_Char_Size(
face, /* face对象的句柄 */
0, /* 以1/64点为单位的字符宽度 */
16*64, /* 以1/64点为单位的字符高度 */
300, /* 设备水平分辨率 */
300 ); /* 设备垂直分辨率 */
* 字符宽度和高度以1/64点为单位表示。一个点是一个1/72英寸的物理距离。通常,这不等于一个象素。
* 设备的水平和垂直分辨率以每英寸点数(dpi)为单位表示。显示设备(如显示器)的常规值为72dpi或96dpi。这个分辨率是用来从字符点数计算字符 象素大小的。* 字符宽度为0意味着“与字符高度相同”,字符高度为0意味着“与字符宽度相同”。对于其他情况则意味着指定不一样的字符宽度和高度。
* 水平或垂直分辨率为0时表示使用默认值72dpi。
* 第一个参数是face对象的句柄,不是size对象的。
error = FT_Set_Pixel_Sizes(
face, /* face对象句柄 */
0, /* 象素宽度 */
16 ); /* 象素高度 */
一个face对象包含一个或多个字符表(charmap),字符表是用来转换字符码到字形索引的。例如,很多TrueType字体包含两个字符表,一个用 来转换Unicode字符码到字形索引,另一个用来转换Apple Roman编码到字形索引。这样的字体既可以用在Windows(使用Unicode)和Macintosh(使用Apple Roman)。同时要注意,一个特定的字符表可能没有覆盖完字体里面的全部字形。
当新建一个face对象时,它默认选择Unicode字符表。如果字体没包含Unicode字符表,FreeType会尝试在字形名的基础上模拟一个。注 意,如果字形名是不标准的那么模拟的字符表有可能遗漏某些字形。对于某些字体,包括符号字体和旧的亚洲手写字体,Unicode模拟是不可能的。
我们将在稍后叙述如何寻找face中特定的字符表。现在我们假设face包含至少一个Unicode字符表,并且在调用FT_New_Face时已经被选 中。我们使用FT_Get_Char_Index把一个Unicode字符码转换为字形索引,如下所示:
glyph_index = FT_Get_Char_Index( face, charcode );
这个函数会在face里被选中的字符表中查找与给出的字符码对应的字形索引。如果没有字符表被选中,这个函数简单的 返回字符码。
注意,这个函数是FreeType中罕有的不返回错误码的函数中的一个。然而,当一个特定的字符码在face中没有字形图像,函数返回0。按照约定,它对 应一个特殊的字形图像――缺失字形,通常会显示一个框或一个空格。
通过调用FT_Load_Glyph来装载一个字形图像到字形槽中,如下:
error = FT_Load_Glyph(
face, /* face对象的句柄 */
glyph_index, /* 字形索引 */
load_flags ); /* 装载标志,参考下面 */
* 如果找到一个对应该字形和象素尺寸的位图,那么它将会被装载到字形槽中。嵌入的位图总是比原生的图像格式优先装载。因为我们假定嵌入的位图对一个字形是比 较高质量的版本。这可以用FT_LOAD_NO_BITMAP标志来改变。
* 否则,将装载一个该字形的原生图像,把它伸缩到当前的象素尺寸,并且对应如TrueType和Type1这些格式,也会完成hinted操作。
error = FT_Render_Glyph( face->glyph, /* 字形槽 */
render_mode ); /* 渲染模式 */
要注意,bitmap_left是从字形位图当前笔位置到最左边界的水平距离,而 bitmap_top是从笔位置(位于基线)到最高边界得垂直距离。他么是正数,指示一个向上的距离。
下一部分将给出字形槽内容的更多细节,以及如何访问特定的字形信息(包括度量)。
error = FT_Select_CharMap(
face, /* 目标face对象 */
FT_ENCODING_BIG5 ); /* 编码 */
通的形式用来描述该字符表。
每一个值组合对应一个特定的编码。例如组合(3,1)对应Unicode。组合列表定义在TrueType规范中,但你也可以使用文件 FT_TRUETYPE_IDS_H来处理它们,该文件定义了几个有用的常数。
要选择一个具体的编码,你需要在规范中找到一个对应的值组合,然后在字符表列表中寻找它。别忘记,由于历史的原因,某些编码会对应几个值组合。这里是一些 代码:
FT_CharMap found = 0;
FT_CharMap charmap;
int n;
for ( n = 0; n < face->num_charmaps; n++ )
{
charmap = face->charmaps[n];
if ( charmap->platform_id == my_platform_id &&
charmap->encoding_id == my_encoding_id )
{
found = charmap;
break ;
}
}
if ( !found ) { ... }
/* 现在,选择face对象的字符表*/
error = FT_Set_CharMap( face, found );
if ( error ) { ... }
error = FT_Set_Transform(
face, /* 目标face对象 */
&matrix, /* 指向2x2矩阵的指针 */
&delta ); /* 指向2维矢量的指针 */
矢量指针也可以设置为NULL,在这种情况下将使用(0, 0)的delta。矢量坐标以一个象素的1/64为单位表示(即通常所说的26.6固定浮点格式)。
注意:变换将适用于使用FT_Load_Glyph装载的全部字形,并且完全独立于任何hinting处理。这意味着你对一个12象素的字形进行2倍放大 变换不会得到与24象素字形相同的结果(除非你禁止hints)。
如果你需要使用非正交变换和最佳hints,你首先必须把你的变换分解为一个伸缩部分和一个旋转/剪切部分。使用伸缩部分来计算一个新的字符象素大小,然 后使用旋转/剪切部分来调用FT_Set_Transform。这在本教程的后面部分有详细解释。
同时要注意,对一个字形位图进行非同一性变换将产生错误。
该程序的思想是建立一个循环,在该循环的每一次迭代中装载一个字形图像,把它转换为一个抗锯齿位图,把它绘制到目标表面(surface)上,然后增加当 前笔的位置。
FT_GlyphSlot slot = face->glyph; /* a small shortcut */
int pen_x, pen_y, n;
... initialize library ...
... create face object ...
... set character size ...
pen_x = 300;
pen_y = 200;
for ( n = 0; n < num_chars; n++ )
{
FT_UInt glyph_index;
/* retrieve glyph index from character code */
glyph_index = FT_Get_Char_Index( face, text[n] );
/* load glyph image into the slot (erase previous one) */
error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );
if ( error )
continue ; /* ignore errors */
/* convert to an anti-aliased bitmap */
error = FT_Render_Glyph( face->glyph, FT_RENDER_MODE_NORMAL );
if ( error )
continue ;
/* now, draw to our target surface */
my_draw_bitmap( &slot->bitmap,
pen_x + slot->bitmap_left,
pen_y - slot->bitmap_top );
/* increment pen position */
pen_x += slot->advance.x >> 6;
pen_y += slot->advance.y >> 6; /* not useful for now */
}
* 我们定义了一个名为slot的句柄,它指向face对象的字形槽。(FT_GlyphSlot类型是一个指针)。这是为了便于避免每次都使用 face->glyph->XXX。
* 我们以slot->advance增加笔位置,slot->advance符合字形的步进宽度(也就是通常所说的走格 (escapement))。步进矢量以象素的1/64为单位表示,并且在每一次迭代中删减为整数象素。* 函数my_draw_bitmap不是FreeType的一部分,但必须由应用程序提供以用来绘制位图到目标表面。在这个例子中,该函数以一个 FT_Bitmap描述符的指针和它的左上角位置为参数。
* Slot->bitmap_top的值是正数,指字形图像顶点与pen_y的垂直距离。我们假定my_draw_bitmap采用的坐标使用一样的 约定(增加Y值对应向下的扫描线)。我们用pen_y减它,而不是加它。
FT_GlyphSlot slot = face->glyph; /* a small shortcut */
FT_UInt glyph_index;
int pen_x, pen_y, n;
... initialize library ...
... create face object ...
... set character size ...
pen_x = 300;
pen_y = 200;
for ( n = 0; n < num_chars; n++ )
{
/* load glyph image into the slot (erase previous one) */
error = FT_Load_Char( face, text[n], FT_LOAD_RENDER );
if ( error )
continue ; /* ignore errors */
/* now, draw to our target surface */
my_draw_bitmap( &slot->bitmap,
pen_x + slot->bitmap_left,
pen_y - slot->bitmap_top );
/* increment pen position */
pen_x += slot->advance.x >> 6;
}
* 我们使用函数FT_Loac_Char代替FT_Load_Glyph。如你大概想到的,它相当于先调用GT_Get_Char_Index然后调用 FT_Get_Load_Glyph。
* 我们不使用FT_LOAD_DEFAULT作为装载模式,使用FT_LOAD_RENDER。它指示了字形图像必须立即转换为一个抗锯齿位图。这是一个捷 径,可以取消明显的调用FT_Render_Glyph,但功能是相同的。
FT_GlyphSlot slot;
FT_Matrix matrix; /* transformation matrix */
FT_UInt glyph_index;
FT_Vector pen; /* untransformed origin */
int n;
... initialize library ...
... create face object ...
... set character size ...
slot = face->glyph; /* a small shortcut */
/* set up matrix */
matrix.xx = (FT_Fixed)( cos ( angle ) * 0x10000L );
matrix.xy = (FT_Fixed)(-sin ( angle ) * 0x10000L );
matrix.yx = (FT_Fixed)( sin ( angle ) * 0x10000L );
matrix.yy = (FT_Fixed)( cos ( angle ) * 0x10000L );
/* the pen position in 26.6 cartesian space coordinates */
/* start at (300,200) */
pen.x = 300 * 64;
pen.y = ( my_target_height - 200 ) * 64;
for ( n = 0; n < num_chars; n++ )
{
/* set transformation */
FT_Set_Transform( face, &matrix, &pen );
/* load glyph image into the slot (erase previous one) */
error = FT_Load_Char( face, text[n], FT_LOAD_RENDER );
if ( error )
continue ; /* ignore errors */
/* now, draw to our target surface (convert position) */
my_draw_bitmap( &slot->bitmap,
slot->bitmap_left,
my_target_height - slot->bitmap_top );
/* increment pen position */
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
* 现在我们使用一个FT_Vector类型的矢量来存储笔位置,其坐标以象素的1/64为单位表示,并且倍增。该位置表示在笛卡儿空间。
* 不同于系统典型的对位图使用的坐标系(其最高的扫描线是坐标0),FreeType中,字形图像的装载、变换和描述总是采用笛卡儿坐标系(这意味着增加Y 对应向上的扫描线)。因此当我们定义笔位置和计算位图左上角时必须在两个系统之间转换。* 我们对每一个字形设置变换来指示旋转矩阵以及使用一个delta来移动转换后的图像到当前笔位置(在笛卡儿空间,不是位图空间)。结 果,bitmap_left和bitmap_top的值对应目标空间象素中的位图原点。因此,我们在调用my_draw_bitmap时不在它们的值上加 pen.x或pen.y。
* 步进宽度总会在变换后返回,这就是它可以直接加到当前笔位置的原因。注意,这次它不会四舍五入。
要很注意,虽然这个例子比前面的更复杂,但是变换效果是完全一致的。因此它可以作为一个替换(但更强大)。
然而该例子有少许缺点,我们将在本教程的下一部分中解释和解决。
结论
在这个部分,你已经学习了FreeType2的基础以及渲染旋转文字的充分知识。
下一部分将深入了解FreeType 2 API更详细的资料,它可以让你直接访问字形度量标准和字形图像,还能让你学习到如何处理缩放、hinting、自居调整,等等。
第三部分将讨论模块、缓存和其他高级主题,如如何在一个face中使用多个尺寸对象。