第一步 -- 简易的字形装载
介绍
这是“FreeType2 教程”的第一部分。它将教会你如何:
* 初始化库
* 通过创建一个新的 face 对象来打开一个字体文件
* 以点或者象素的形式选择一个字符大小
* 装载一个字形(glyph)图像,并把它转换为位图
* 渲染一个简单的字符串
* 容易地渲染一个旋转的字符串
1.头文件
下面的内容是编译一个使用了FreeType2库的应用程序所需要的指令。请谨慎阅读,自最近一次版本更新后我们已经更改了少许东西。
1.FreeType2 include 目录
你必须把FreeType2头文件的目录添加到编译包含(include)目录中。
注意,现在在Unix系统,你可以运行freetype-config脚本加上--cflags选项来获得正确的编译标记。这个脚本也可以用来检查安装在你系统中的库的版本,以及需要的库和连接标记。
2. 包含名为ft2build.h的文件
Ft2build.h包含了接下来要#include的公共FreeType2头文件的宏声明。
3. 包含主要的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>
. . .
2. 初始化库
简单地创建一个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。
3.装载一个字体face
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 )
{
... 其它的错误码意味着这个字体文件不能打开和读,或者简单的说它损坏了...
}
就如你所想到的,FT_NEW_Face打开一个字体文件,然后设法从中提取一个face。它的参数为:
Library
一个FreeType库实例的句柄,face对象从中建立
Filepathname
字体文件路径名(一个标准的C字符串)
Face_index
某些字体格式允许把几个字体face嵌入到同一个文件中。
这个索引指示你想装载的face。
如果这个值太大,函数将会返回一个错误。Index 0总是正确的。
Face
一个指向新建的face对象的指针。
当失败时其值被置为NULL。
要知道一个字体文件包含多少个face,只要简单地装载它的第一个face(把face_index设置为0),face->num_faces的值就指示出了有多少个face嵌入在该字体文件中。
b.从内存装载
如果你已经把字体文件装载到内存,你可以简单地使用FT_New_Memory_Face为它新建一个face对象,如下所示:
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 ) { ... }
如你所看到的,FT_New_Memory_Face简单地用字体文件缓存的指针和它的大小(以字节计算)代替文件路径。除此之外,它与FT_New_Face的语义一致。
c.从其他来源装载(压缩文件,网络,等)
使用文件路径或者预装载文件到内存是简单的,但还不足够。FreeType 2可以支持通过你自己实现的I/O程序来装载文件。
这是通过FT_Open_Face函数来完成的。FT_Open_Face可以实现使用一个自定义的输入流,选择一个特定的驱动器来打开,乃至当创建该对象时传递外部参数给字体驱动器。我们建议你查阅“FreeType 2参考手册”,学习如何使用它。
4.访问face内容
一个face对象包含该face的全部全局描述信息。通常的,这些数据可以通过分别查询句柄来直接访问,例如face->num_glyphs。
FT_FaceRec结构描述包含了可用字段的完整列表。我们在这里详细描述其中的某些:
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的水平和垂直字符象素尺寸。
注意,通常来说,这不是位图strike的单元尺寸。
5.设置当前象素尺寸
对于特定face中与字符大小相关的信息,FreeType 2使用size对象来构造。例如,当字符大小为12点时,使用一个size对象以1/64象素为单位保存某些规格(如ascender或者文字高度)的值。
当FT_New_Face或它的亲戚被调用,它会自动在face中新建一个size对象,并返回。该size对象可以通过face->size直接访问。
注意:一个face对象可以同时处理一个或多个size对象,但只有很少程序员需要用到这个功能,因而,我们决定简化该API,(例如,每个face对象只拥有一个size对象)但是这个特性我们仍然通过附加的函数提供。
当一个新的face对象建立时,对于可伸缩字体格式,size对象默认值为字符大小水平和垂直均为10象素。对于定长字体格式,这个大小是未定义的,这就是你必须在装载一个字形前设置该值的原因。
使用FT_Set_Char_Size完成该功能。这里有一个例子,它在一个300x300dpi设备上把字符大小设置为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对象的。
这个函数计算对应字符宽度、高度和设备分辨率的字符象素大小。然而,如果你想自己指定象素大小,你可以简单地调用FT_Set_Pixel_Sizes,就像这样:
error = FT_Set_Pixel_Sizes(
face, /* face对象句柄 */
0, /* 象素宽度 */
16 ); /* 象素高度 */
这个例子把字符象素设置为16x16象素。如前所说的,尺寸中的任一个为0意味着“与另一个尺寸值相等”。
注意这两个函数都返回错误码。通常,错误会发生在尝试对定长字体格式(如FNT或PCF)设置不在face->fixed_size数组中的象素尺寸值。
6.装载一个字形图像
a.把一个字符码转换为一个字形索引
通常,一个应用程序想通过字符码来装载它的字形图像。字符码是一个特定编码中代表该字符的数值。例如,字符码64代表了ASCII编码中的’A’。
一个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。按照约定,它对应一个特殊的字形图像――缺失字形,通常会显示一个框或一个空格。
b.从face中装载一个字形
一旦你获得了字形索引,你便可以装载对应的字形图像。在不同的字体中字形图像存储为不同的格式。对于固定尺寸字体格式,如FNT或者PCF,每一个图像都是一个位图。对于可伸缩字体格式,如TrueType或者Type1,使用名为轮廓(outlines)的矢量形状来描述每一个字形。一些字体格式可能有更特殊的途径来表示字形(如MetaFont――但这个格式不被支持)。幸运的,FreeType2有足够的灵活性,可以通过一个简单的API支持任何类型的字形格式。
字形图像存储在一个特别的对象――字形槽(glyph slot)中。就如其名所暗示的,一个字形槽只是一个简单的容器,它一次只能容纳一个字形图像,可以是位图,可以是轮廓,或者其他。每一个face对象都有一个字形槽对象,可以通过face->glyph来访问。它的字段在FT_GlyphSlotRec结构的文档中解释了。
通过调用FT_Load_Glyph来装载一个字形图像到字形槽中,如下:
error = FT_Load_Glyph(
face, /* face对象的句柄 */
glyph_index, /* 字形索引 */
load_flags ); /* 装载标志,参考下面 */
load_flags的值是位标志集合,是用来指示某些特殊操作的。其默认值是FT_LOAD_DEFAULT即0。
这个函数会设法从face中装载对应的字形图像:
* 如果找到一个对应该字形和象素尺寸的位图,那么它将会被装载到字形槽中。嵌入的位图总是比原生的图像格式优先装载。因为我们假定对一个字形,它有更高质量的版本。这可以用FT_LOAD_NO_BITMAP标志来改变。
* 否则,将装载一个该字形的原生图像,把它伸缩到当前的象素尺寸,并且对应如TrueType和Type1这些格式,也会完成hinted操作。
字段face->glyph->format描述了字形槽中存储的字形图像的格式。如果它的值不是FT_GLYPH_FORMAT_BITMAP,你可以通过FT_Render_Glyph把它直接转换为一个位图。如下:
error = FT_Render_Glyph( face->glyph, /* 字形槽 */
render_mode ); /* 渲染模式 */
render_mode参数是一个位标志集合,用来指示如何渲染字形图像。把它设为FT_RENDER_MODE_NORMAL渲染出一个高质量的抗锯齿(256级灰度)位图。这是默认情况,如果你想生成黑白位图,可以使用FT_RENDER_MODE_MONO标志。
一旦你生成了一个字形图像的位图,你可以通过glyph->bitmap(一个简单的位图描述符)直接访问,同时用glyph->bitmap_left和glyph->bitmap_top来指定起始位置。
要注意,bitmap_left是从字形位图当前笔位置到最左边界的水平距离,而bitmap_top是从笔位置(位于基线)到最高边界得垂直距离。他么是正数,指示一个向上的距离。
下一部分将给出字形槽内容的更多细节,以及如何访问特定的字形信息(包括度量)。
c.使用其他字符表
如前面所说的,当一个新face对象创建时,它会寻找一个Unicode字符表并且选择它。当前被选中的字符表可以通过face->charmap访问。当没有字符表被选中时,该字段为NULL。这种情况在你从一个不含Unicode字符表的字体文件(这种文件现在非常罕见)创建一个新的FT_Face对象时发生。
有两种途径可以在FreeType 2中选择不同的字符表。最轻松的途径是你所需的编码已经有对应的枚举定义在FT_FREETYPE_H中,例如FT_ENCODING_BIG5。在这种情况下,你可以简单地调用FT_Select_CharMap,如下:
error = FT_Select_CharMap(
face, /* 目标face对象 */
FT_ENCODING_BIG5 ); /* 编码 */
另一种途径是手动为face解析字符表。这通过face对象的字段num_charmaps和charmaps(注意这是复数)来访问。如你想到的,前者是face中的字符表的数目,后者是一个嵌入在face中的指向字符表的指针表(a table of pointers to the charmaps)。
每一个字符表有一些可见的字段,用来更精确地描述它,主要用到的字段是charmap->platform_id和charmap->encoding_id。这两者定义了一个值组合,以更普
通的形式用来描述该字符表。
每一个值组合对应一个特定的编码。例如组合(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 ) { ... }
一旦某个字符表被选中,无论通过FT_Select_CharMap还是通过FT_Set_CharMap,它都会在后面的FT_Get_Char_Index调用使用。
d.字形变换
当字形图像被装载时,可以对该字形图像进行仿射变换。当然,这只适用于可伸缩(矢量)字体格式。
简单地调用FT_Set_Transform来完成这个工作,如下:
error = FT_Set_Transform(
face, /* 目标face对象 */
&matrix, /* 指向2x2矩阵的指针 */
&delta ); /* 指向2维矢量的指针 */
这个函数将对指定的face对象设置变换。它的第二个参数是一个指向FT_Matrix结
构的指针。该结构描述了一个2x2仿射矩阵。第三个参数是一个指向FT_Vector结构的指针。该结构描述了一个简单的二维矢量。该矢量用来在2x2变换后对字形图像平移。
注意,矩阵指针可以设置为NULL,在这种情况下将进行恒等变换。矩阵的系数是16.16形式的固定浮点单位。
矢量指针也可以设置为NULL,在这种情况下将使用(0, 0)的delta。矢量坐标以一个象素的1/64为单位表示(即通常所说的26.6固定浮点格式)。
注意:变换将适用于使用FT_Load_Glyph装载的全部字形,并且完全独立于任何hinting处理。这意味着你对一个12象素的字形进行2倍放大变换不会得到与24象素字形相同的结果(除非你禁止hints)。
如果你需要使用非正交变换和最佳hints,你首先必须把你的变换分解为一个伸缩部分和一个旋转/剪切部分。使用伸缩部分来计算一个新的字符象素大小,然后使用旋转/剪切部分来调用FT_Set_Transform。这在本教程的后面部分有详细解释。
同时要注意,对一个字形位图进行非同一性变换将产生错误。
7. 简单的文字渲染
现在我们将给出一个非常简单的例子程序,该例子程序渲染一个8位Latin-1文本字符串,并且假定face包含一个Unicode字符表。
该程序的思想是建立一个循环,在该循环的每一次迭代中装载一个字形图像,把它转换为一个抗锯齿位图,把它绘制到目标表面(surface)上,然后增加当前笔的位置。
a.基本代码
下面的代码完成我们上面提到的简单文本渲染和其他功能。
FT_GlyphSlot slot = face->glyph; /* 一个小捷径 */
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;
/* 从字符码检索字形索引 */
glyph_index = FT_Get_Char_Index( face, text[n] );
/* 装载字形图像到字形槽(将会抹掉先前的字形图像) */
error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );
if ( error )
continue; /* 忽略错误 */
/* 转换为一个抗锯齿位图 */
error = FT_Render_Glyph( face->glyph, ft_render_mode_normal );
if ( error )
continue;
/* 现在,绘制到我们的目标表面(surface) */
my_draw_bitmap( &slot->bitmap,
pen_x + slot->bitmap_left,
pen_y - slot->bitmap_top );
/* 增加笔位置 */
pen_x += slot->advance.x >> 6;
pen_y += slot->advance.y >> 6; /* 现在还是没用的 */
}
这个代码需要一些解释:
* 我们定义了一个名为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减它,而不是加它。
b.精练的代码
下面的代码是上面例子程序的精练版本。它使用了FreeType 2中我们还没有介绍的特性和函数,我们将在下面解释:
FT_GlyphSlot slot = face->glyph; /* 一个小捷径 */
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++ )
{
/* 装载字形图像到字形槽(将会抹掉先前的字形图像) */
error = FT_Load_Char( face, text[n], FT_LOAD_RENDER );
if ( error )
continue; /* 忽略错误 */
/* 现在,绘制到我们的目标表面(surface) */
my_draw_bitmap( &slot->bitmap,
pen_x + slot->bitmap_left,
pen_y - slot->bitmap_top );
/* 增加笔位置 */
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_LOAD_MONOCHROME装载标志来获得一个单色位图。
c.更高级的渲染
现在,让我们来尝试渲染变换文字(例如通过一个环)。我们可以用FT_Set_Transform来完成。这里是示例代码:
FT_GlyphSlot slot;
FT_Matrix matrix; /* 变换矩阵 */
FT_UInt glyph_index;
FT_Vector pen; /* 非变换原点 */
int n;
... initialize library ...
... create face object ...
... set character size ...
slot = face->glyph; /* 一个小捷径 */
/* 准备矩阵 */
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 );
/* 26.6 笛卡儿空间坐标中笔的位置,以(300, 200)为起始 */
pen.x = 300 * 64;
pen.y = ( my_target_height - 200 ) * 64;
for ( n = 0; n < num_chars; n++ )
{
/* 设置变换 */
FT_Set_Transform( face, &matrix, &pen );
/* 装载字形图像到字形槽(将会抹掉先前的字形图像) */
error = FT_Load_Char( face, text[n], FT_LOAD_RENDER );
if ( error )
continue; /* 忽略错误 */
/* 现在,绘制到我们的目标表面(变换位置) */
my_draw_bitmap( &slot->bitmap,
slot->bitmap_left,
my_target_height - slot->bitmap_top );
/* 增加笔位置 */
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中使用多个尺寸对象。【这部分还没有编写】
国人OpenGL下作开发,一个或许是必须要做的事情就是显示汉字。如果你的产品只是活动在win32平台下,wgl开头的windows辅助函数会帮你的大忙。
如果希望能够摆脱对win32 api的依赖。FreeType可能是你最佳的选择,除非你不在乎不用TTF。中文点阵字体在小字号的时候有得天独厚的优势,但是大字体的时候毫无疑问是TrueType的天下。
我对这个问题其实关注已久,FreeType每次都是拿起又放下,也试过自己从TreeType下提取点阵字体,以求能够解决这个问题。哈~
但是前几天我不经意找到了VTK这个暴强的三维可视化库,这个问题又回到了我的眼前。
VTK的文字引擎后台用的是FTGL,FTGL的后端是FreeType2。一番实践之后,我知道这个库还是对中文支持的不够好。事实上,如果使用unicode(utf16/ucs2)的编码,FTGL已经可以很好的显示中文。不过前提是使用wchar_t。很多时候我们还是使用传统的char,还有一个原因就是中文字体所带的英文显示效果很难看。这个如果你在linux的环境下作过美化一定印象深刻。
基于以上两点,我动手对ftgl进行修改,增加gbk转换unicode的工作,并在真正render之前,选择合适的字体分别渲染中文和英文。还作了一件事,就是把vtk所带的freetype2和ftgl大大的升级了一把。然后修改了vtk的OpenGLFontCache和OpenGLTextMapper类,初步的效果如下:(使用了simsun.ttc和Tahoma.ttf)效果的确不错!
就是现在的代码patch的不规范,有空再整理吧。
ps:borland的C++ Complier对C++的支持其实满不错。但就是很多opensource的开发者,在windows下的port一般都是使用vc的编译器,自己手动编译到borland下来很复杂。
vtk作为一个跨平台的开发工具。使用了自己的make组织工具--CMaker,类似于jam的东西。我发现这个东西的确不错。以后有空多多学习。
FreeType:http://www.freetype.org/
FTGL: http://homepages.paradise.net.nz/henryj/code/
VTK的网站:http://www.vtk.org/
cmake的网站: http://www.cmake.org/
我这里上去暴慢,动不动就10061:Socket Connection Error 拒绝访问。郁闷!
BTW: 本文提到的东西都是open source的,除了那两个字体 -_-|||。
FreeType2的简单使用:
FreeType2是一个简单的跨平台的字体绘制引擎.目前支持TrueType Type1 Type2等字体格式.不过目前好象还不支持OpenType.
使用FreeType的应用很多.著名的FTGL就是使用FreeType的.能在OpenGL高效率的绘制矢量字体.
FTGL我没用过.因为不想在没了解该怎么用FreeType的情况下就去用FTGL.
经过一个晚上的阅读代码(我的代码阅读能力是很差的).终于知道了如何使用FreeType2了。不过是简单的使用,还不知道如何设置Bold Itainly等属性.主要是简单的演示.以后准备做成一个完善的字体引擎.
下面简单的介绍一下.
首先当然是包含头文件了。头文件要这样包含:
#include [ft2build.h]
#include FT_FREETYPE_H
不知道为什么.反正就是要这么包含.
以下为FT2的初始化代码.和绘制以及释放的代码>
注意这里绘制代码接受的字符是Unicode.表示你这样旧可以绘制了
FT2_Obj font;
font.Init("SimSun.ttf",32);
wchat_t pText[]=L"潘李亮是一头野猪";
for(int n = 0 ; n< wcslen(pText);n++)
{
font.DrawAUnicode(pText[n];
}
font.Free();
//以下为FT2_Obj的代码.
//主要参考了Nehe的Lesson 43
class FT2_Obj
{
FT_Library library;
int h ;
FT_Face face;
public:
void Init(const char * fname, unsigned int h);
void Free();
void DrawAUnicode(wchar_t ch)
};
void FT2_Obj::Init(const char * fname, unsigned int h)
{
this->h=h;
//初始化FreeType库..
if (FT_Init_FreeType( &library ))
throw std::runtime_error("FT_Init_FreeType failed");
//加载一个字体,取默认的Face,一般为Regualer
if (FT_New_Face( library, fname, 0, &face ))
throw std::runtime_error("FT_New_Face failed (there is probably a problem with your font file)");
//大小要乘64.这是规定。照做就可以了。
FT_Set_Char_Size( face,h<< 6, h << 6, 96, 96);
FT_Matrix matrix; /* transformation matrix */
FT_UInt glyph_index;
FT_Vector pen;
//给它设置个旋转矩阵
float angle = -20/180.* 3.14;
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 );
FT_Set_Transform( face, &matrix, &pen );
}.
/*
绘制操作.
*/
void FT2_Obj::DrawAUnicode(wchar_t ch)
{
if(FT_Load_Glyph( face, FT_Get_Char_Index( face, ch ), FT_LOAD_DEFAULT ))
throw std::runtime_error("FT_Load_Glyph failed");
//得到字模
FT_Glyph glyph;
if(FT_Get_Glyph( face->glyph, &glyph ))
throw std::runtime_error("FT_Get_Glyph failed");
//转化成位图
FT_Render_Glyph( face->glyph, FT_RENDER_MODE_NORMAL );
FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 );
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;
//取道位图数据
FT_Bitmap& bitmap=bitmap_glyph->bitmap;
//把位图数据拷贝自己定义的数据区里.这样旧可以画到需要的东西上面了。
int width = bitmap.width;
int height = bitmap.rows;
usigned char* expanded_data = new usigned char[ 3 * width * height];
for(int j=0; j
< height ; j++)
{
for(int i=0; i < width; i++)
{
expanded_data[3*(i+(height-j-1)*width)]=
expanded_data[3*(i+(height-j-1)*width)+1] =
expanded_data[3*(i+(height-j-1)*width)+2] =
(i>=bitmap.width || j>=bitmap.rows) ?
0 : bitmap.buffer[i + bitmap.width*j];
}
}
}
}
void FT2_Obj::Free()
{
FT_Done_Face(face);
FT_Done_FreeType(library);
}
最近学习状态不佳,感觉什么都想做却什么也做不下去,浮躁之极。大的库一下子研究不下来,索性找一下小库来看看。 FreeType 2 Library FAQ (当前下载地址: http://sourceforge.net/project/showfiles.php?group_id=3157版本 2.2.1 ) 1、 FreeType2 是什么? 它是一个为各种应用程序提供通用的字体文件访问的软件包。尤其值得注意的以下特性: l 提供统一的字体文件访问接口。支持位图和向量格式,包括 TrueType 、 OpenType 、 Typel 、 CID 、 CFF 、 Windows FON/FNT 、 X11 PCF 。 l 提供高效反走样的基于 256 灰度级的位图字形的生产。 l 模块清晰,每种字体格式对于一个模块。类库的构建可以按照你需要支持的格式进行裁减以减小代码尺寸。(最小的反走样 FreeType 库 <30Kb ) 2、 FreeType2 能做什么? FT2 已经易用于许多领域。例如: l 图形子系统和文本显示库 l 文本排版(布局、分页、渲染) l 字体识别和转换工具 一般来说,该库使得你能轻松的操纵字体文件。 3、 FreeType2 不能做什么? FT2 并不包含大量丰富的高级特性,它只定位于出色的字体服务。也就是说下面的一些特性 FT2 类库并不直接提供支持,然而你可以以它为基础在上层进行实现: l 任意表面的文字渲染 FT2 不是图形库所以它仅支持两种象素格式的文本渲染: 1-bit 的单色位图和 8-bit 的灰度象素。 如果你需要绘制其它格式的表面(例如 24-bit RGB 象素),你就得选择其它你喜爱的图形库来做。 注意:为了渲染向量轮廓文本而不是放走样的象素,应用程序可以提供自己的渲染回调以绘制或者直接组合反走样文本到任意目标表面。 l 文本缓存 每次从字体中请求文本图象, FT2 都要解析字体文件 / 流相关部分,通过它的字体格式进行解释。对于某些特殊格式可能会很慢包括像 TrueType (或者 Type1 )这样的向量字体。 注意:自从 2.0.1 版本开始 FT2 提供了一个 beta 版本的缓存子系统。当然你还是可以写自己的缓存来满足某种特殊需求。 l 文本布局 不支持文本布局操作。高级操作例如文本替换、字距调整、两端调整等都不属于字体服务本身职责。 4、 FreeType2 可移植性? FT2 源码可移植性很好由于以下原因: l 代码书写遵循 ANSI C 标准 l 对于各种编译警告我们都谨慎的避免。当前代码在很多编译器上编译通过且没有产生一条警告。 l 库没有使用任何硬编码,是嵌入式系统开发的一个好的选择。(例如它能够直接在 ROM 中运行) 同时,我们尽最大努力确保库的高效、紧凑和友好性。 5、 FreeType2 与 FreeType1.x 的区别? 最大的区别就是: l FT1 仅支持 TrueType 格式,而 FT2 支持很多格式。 l FT2 APIs 比 FT1 APIs 简单且强大。 l FT1 包括 OpenType 文本布局处理扩展,而 FT2 中则不包括而是移到独立的工程里面―― FreeType Layout 。( FT 布局目前无法获取) 6、 FreeType2 是否兼容 FreeType 1.x ? FreeType2 不直接兼容 FreeType 1.x ,但是我们可以提供一个二进制兼容层使得应用程序重链接到新版本。我们最终放弃了这种想法因为两个版本可以共存在一个系统中。(没有命名冲突) FT2 API 比 1.x 简单且强大,所以我们鼓励你采用新版本,这样可以使你减少很多不必要的工作。 7、 是否可以使用 FreeType2 编辑字体或者创建新字体? 答案是明确的:不可以。因为该库设计明确,用较少代码和内存读取字体文件。所以我们不打算以任何方式在字体引擎里面支持编辑或者创建功能,因为这样将导致整个代码重写。这并不意味我们将来不会引入字体编辑 / 创建功能库,这取决于需求(或者说有多少人愿意为此买单)。 在我们正式发布前不要在这方面进行揣测,对我们而言这个项目存在其他一些更重要的部分需要解决(像文字布局、文本缓存)。 编译 & 配置 1、 如何编译 FreeType2 库? 可以采取多种编译方式,在 freetype2/docs/build 下有详细说明文档。 这里介绍最简单的基于 VS IDE 的编译方式。 freetype\builds\win32\visualc 下有 VC6 和 VC7.1 的工作区文件。 VC6 打开后直接编译,有几个警告。 ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
在FTGL的基础上写了一个在OpenGL下显示TureType文字的库——TTGLFont。
主要是改进了在初次使用每个字符时把字符缓存到纹理中,通过绘制带纹理的四边形来显示文字。文字渲染引擎使用的是FreeType2.3.5。支持UNICODE。支持字符串中的换行符换行。目前显示文字的速度比FTGL有一定提高。接下来准备用vertex arry来进一步提高文字绘制速度。
压缩包里包括源代码和一个演示程序,对比FTGL和TTGLFont的效果。程序默认使用系统目录的\fonts\ARIALUNI.TTF 字体文件,也可以使用命令行参数来指定使用的字体文件。不过使用simsun.ttc字体时由于FTGL的bug,只能正常显示TTGLFont字体,而FTGL会乱掉。
这个库使用比较简单:
//包含头文件和FreeType库文件。
#include "TTGLFont\include\TTGLFont.h"
#pragma comment( lib, "freetype.lib" )
//在建立好OpenGL环境后初始化TTGLFont。
TTGLFont font=new TTGLFont("c:\windows\fonts\ARIALUNI.TTF",18); //设置字体文件和字号
//设置OpenGL的alpha混合和纹理环境。
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0);
glEnable(GL_BLEND);
glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
//设置投影模式。
glMatrixMode( GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, win_width, win_height, 0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//绘制文字。
font->Render(L"render fonts in OpenGL\n在OpenGL 中绘制文字"); //中文使用UNICODE。
//还可以设置行间距。
font->LineOffset(5); //默认行间距+LineOffset。
FT_Library pFTLib = NULL; FT_Face pFTFace = NULL;
FT_Error error = 0 ;
// Init FreeType Lib to manage memory
error = FT_Init_FreeType( & pFTLib);
if (error)
{
pFTLib = 0 ;
printf( " There is some error when Init Library
" );
return - 1 ;
}
// create font face from font file
error = FT_New_Face(pFTLib, " C:\\WINDOWS\\Fonts\\arial.ttf " , 0 , & pFTFace);
if ( ! error)
{
FT_Set_Char_Size(pFTFace, 16 << 6 , 16 << 6 , 300 , 300 );
FT_Glyph glyph;
// load glyph 'C'
FT_Load_Glyph(pFTFace, FT_Get_Char_Index(pFTFace, 67 ), FT_LOAD_DEFAULT);
error = FT_Get_Glyph(pFTFace -> glyph, & glyph);
if ( ! error)
{
// convert glyph to bitmap with 256 gray
FT_Glyph_To_Bitmap( & glyph, ft_render_mode_normal, 0 , 1 );
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;
FT_Bitmap & bitmap = bitmap_glyph -> bitmap;
for ( int i = 0 ; i < bitmap.rows; ++ i)
{
for ( int j = 0 ; j < bitmap.width; ++ j)
{
// if it has gray>0 we set show it as 1, o otherwise
printf( " %d " , bitmap.buffer[i * bitmap.width + j] ? 1 : 0 );
}
printf( " \n " );
}
// free glyph
FT_Done_Glyph(glyph);
glyph = NULL;
}
// free face
FT_Done_Face(pFTFace);
pFTFace = NULL;
}
// free FreeType Lib
FT_Done_FreeType(pFTLib);
pFTLib = NULL;
不时看到有人询问如何在OpenGL下显示中文,
正好我前段时间在VTK中显示中文的时候,知道
这个库,推荐给大家。
FTGL是OpenSource的函数库,它在OpenGL下显示矢量字,
这主要通过调用另外一个OpenSource库FreeType实现。
要显示中文,使用非常简单:
1)设置字体文件如simsun.ttc为宋体,simhei.ttf为黑体
2)设定编码方式为Unicode模式
3)定义Unicode(双字节)的字符串
示例:
FTFontfont;
font.open("c:\\winnt\\font\\simhei.ttf");
font.CharMap(ft_encoding_unicode);
wchar_tstr="中文";
font.render(str);
******************
上面只是简单示例,下载FTGLdemo,略作修改即可以显示中文。
由于它使用形如.ttf,.ttc的字体文件,因此可以显示各种字体,
而且有多种显示方式(包括三维字体)