一、本文目的
以前的文档中、详细的介绍了FreeType开源字体引擎库的基础知识、基本用法、但并未详细的阐明在TurboCG中、是如何解析出一个文字的轮廓的,本文集中阐述、怎么样使用FreeType开源字体引擎库、读取一个文字的轮廓、获取轮廓关键点(控制点)之后,解析这些关键点;并使用Qt作为辅助GUI接口、绘制出字体的轮廓。
本文虽然集中讲解文字轮廓处理、但为了完整性,也会介绍怎么初始化字体库等等,通过本文的学习、读者能够快速的了解到使用FreeType的步骤流程,并能够使用FreeType进行文字处理,本文包含了使用FreeType的所有基本API调用的全部内容,是一篇短小实用的指南。
二、FreeType库简介
(一)、FreeType架构结构简介
FT可以看作是一组组件,每个组件负责一部分任务,它们包括 :
1、 客户应用程序一般会调用FT高层API,它的功能都在一个组件中,叫做基础层。
2、 根据上下文和环境,基础层会调用一个或多个模块进行工作,大多数情况下,客户应用程序不知道使用那个模块。
3、基础层还包含一组例程来进行一些共通处理,例如内存分配,列表处理、io流解析、固定点计算等等,这些函数可以被模块随意调用,它们形成了一个底层基础API。
(二)、FT中的面向对象
虽然FT是使用ANSI C编写,但是采用面向对象的思想,所以这个库非常容易扩展,因此,下面有一些代码规约。
1、每个对象类型/类都有一个对应的结构类型和一个对应的结构指针类型,后者称为类型或类的句柄类型
设想我们需要管理FT中一个foo类的对象,可以定义如下 :
typedef struct FT_FooRec_* FT_Foo;
typedef struct FT_FooRec_
{
// fields for the foo class
…
}
FT_FooRec; 依照规约,句柄类型使用简单而有含义的标识符,并以FT_开始,如FT_Foo,而结构体使用相同的名称但是加上Rec后缀。Rec是记录的缩写。每个类类型都有对应的句柄类型
2、FT_Library类
这个类型对应一个库的单一实例句柄,没有定义相应的FT_LibraryRec,使客户应用无法访问它的内部属性。库对象是所有FT其他对象的父亲,你需要在做任何事情前创建一个新的库实例,销毁它时会自动销毁他所有的孩子,如face和module等。 通常客户程序应该调用FT_Init_FreeType()来创建新的库对象,准备作其他操作时使用。另一个方式是通过调用函数FT_New_Library()来创建一个新的库对象,它在<freetype/ftmodule.h>中定义,这个函数返回一个空的库,没有任何模块注册,你可以通过调用FT_Add_Module()来安装模块。调用FT_Init_FreeType()更方便一些,因为他会缺省地注册一些模块。这个方式中,模块列表在构建时动态计算,并依赖ftinit部件的内 容。(见ftinit.c[l73]行,include FT_CONFIG_MODULES_H,其实就是包含ftmodule.h,在ftmodule.h中定义缺省的模块,所以模块数组ft_default_modules的大小是在编译时动态确定的。)
3、FT_Face类
一个外观对象对应单个字体外观,即一个特定风格的特定外观类型,例如Arial和Arial Italic是两个不同的外观。一个外观对象通常使用FT_New_Face()来创建,这个函数接受如下参数:一个FT_Library句柄,一个表示字体文件的C文件路径名,一个决定从文件中装载外观的索引(一个文件中可能有不同的外观),和FT_Face句柄的地址,它返回一个错误码。
FT_Error FT_New_Face( FT_Library library,
const char* filepathname,
FT_Long face_index,
FT_Face* face);
函数调用成功,返回0,face参数将被设置成一个非NULL值。 外观对象包含一些用来描述全局字体数据的属性,可以被客户程序直接访问。例如外观中字形的数量、外观家族的名称、风格名称、EM大小等,详见FT_FaceRec定义。
4、FT_Size类
每个FT_Face对象都有一个或多个FT_Size对象,一个尺寸对象用来存放指定字符宽度和高度的特定数据,每个新创建的外观对象有一个尺寸,可以通过face->size直接访问。尺寸对象的内容可以通过调用FT_Set_Pixel_Sizes()或FT_Set_Char_Size()来改变。 一个新的尺寸对象可以通过FT_New_Size()创建,通过FT_Done_Size()销毁,一般客户程序无需做这一步,它们通常可以使用每个FT_Face缺省提供的尺寸对象。 FT_Size 公共属性定义在FT_SizeRec中,但是需要注意的是有些字体驱动定义它们自己的FT_Size的子类,以存储重要的内部数据,在每次字符大小改变时 计算。大多数情况下,它们是尺寸特定的字体hint。例如,TrueType驱动存储CVT表,通过cvt程序执行将结果放入TT_Size结构体中,而 Type1驱动将scaled global metrics放在T1_Size对象中。
5、FT_GlyphSlot类
字形槽的目的是提供一个地方,可以很容易地一个个地装入字形映象,而不管它的格式(位图、向量轮廓或其他)。理想的,一旦一个字形槽创建了,任何字形映象可以装入,无需其他的内存分配。在实际中,只对于特定格式才如此,像TrueType,它显式地提供数据来计算一个槽地最大尺寸。
另一个字形槽的原因是用他来为指定字形保存格式特定的hint,以及其他为正确装入字形的必要数据。基本的FT_GlyphSlotRec结构体只向客户程序展现了字形metics和映象,而真正的实现回包含更多的数据。例如,TrueType特定的TT_GlyphSlotRec结构包含附加的属性,存放字形特定的字节码、在hint过程中暂时的轮廓和其他一些东西。最后,每个外观对象有一个单一字形槽,可以用face->glyph直接访问。
三、字体轮廓描述
(一)、轮廓曲线分解
一个轮廓是2D平面上一系列封闭的轮廓线。每个轮廓线由一系列线段和Bezier弧组成,根据文件格式不同,曲线可以是二次和三次多项式,前者叫quadratic或conic弧,它们在TrueType格式中用到,后者叫cubic弧,多数用于Type1格式。
每条弧由一系列起点、终点和控制点描述,轮廓的每个点有一个特定的标记,表示它用来描述一个线段还是一条弧。这个标记可以有以下值:
FT_Curve_Tag_On当点在曲线上,这对应线段和弧的起点和终点。其他标记叫做“Off”点,即它不在轮廓线上,但是作为Bezier弧的控制点。
FT_Curve_Tag_Conic一个Off点,控制一个conic Bezier弧
FT_Curve_Tag_Cubic 一个Off点,控制一个cubicBezier弧
下面的规则应用于将轮廓点分解成线段和弧
A 、 两个相邻的“on”点表示一条线段;
B、 一个conic Off点在两个on点之间表示一个conic Bezier弧,off点是控制点,on点是起点和终点;
C、 两个相邻的cubic off点在两个on点之间表示一个cubic Bezier弧,它必须有两个cubic控制点和两个on点。
D、最后,两个相邻的conic off点强制、在它们正中间创建一个虚拟的on点。这大大方便定义连续的conic弧。
typedef struct FT_Outline_
{
short n_contours; 轮廓中轮廓线数
short n_points; 轮廓中的点数
FT_Vector* points; 点坐标数组
char* tags;
short* contours; 轮廓线端点索引数组
int flags; 点标记数组
} FT_Outline;
这里,points是一个FT_Vector记录数组的指针,用来存储每个轮廓点的向量坐标。它表示为一个象素1/64,也叫做26.6固定浮点格式。
contours是一组点索引,用来划定轮廓的轮廓线。例如,第一个轮廓线总是从0点开始,以contours[0]点结束。第二个轮廓线从contours[0]+1点开始,以contours[1]结束,等等。 注意,每条轮廓线都是封闭的,n_points应该和contours[n_controus-1]+1相同。最后,tags是一组字节,用来存放每个轮廓的点标记。
四、 使用FreeType库进行文字轮廓解析实例代码
(一)、FreeType字体库初始。
A、初始化库 FT_Library
FT_Library library
FT_Error error =FT_Init_FreeType( &library );
B、初始化 FT_Face face
FT_Error error = FT_New_Face( library, "C:\Windows\Fonts\msuighur.ttf", 0,&face );
创建一个宋体的FT_FACE。
C、设置所要绘制的文字的大小。
设置字体大小,有两种方式,一种是设置尺寸,是用长度、作为度量单位的,另一种方式,是设置像素个数,字体的宽高、以像素来作为度量单位。
直接用用像素作为度量单位来设置字体大小:
FT_Set_Pixel_Sizes(face,560,560);
(二)、Qt绘制字体轮廓线。
通过上面的的设置、就可以使用FreeType来或者文字的轮廓了。获取英文字母“J”的轮廓字槽方法如下:
int charX= 'J';
intiGlyphIndex = FT_Get_Char_Index(face,charX);
FT_Int32 loadflags =FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP;
FT_Error error = FT_Load_Glyph(face,iGlyphIndex,loadflags);
FT_GlyphSlot pGlyphSlot = face->glyph;
FT_Outline* outline = &pGlyphSlot->outline;
至此、字的轮廓信息完全存储在outline中,下面的代码就是拿来解析,并渲染轮廓的。解析的原理,在上一章节中,已经有了具体的阐述、这里只是代码实现,因此,就不再介绍解析的原理了。
QPainter painter(this);
painter.translate(400, 400);
FT_Vector* point;
FT_Vector* limit;
char* tags;
FT_Vector v_last;
FT_Vector v_control;
FT_Vector v_start;
int first= 0;
for(int n = 0; n < outline->n_contours; n++)
{
int last =outline->contours[n];
limit= outline->points + last;
v_start= outline->points[first];
v_last = outline->points[last];
v_control= v_start;
point= outline->points + first;
tags = outline->tags + first;
char tag =FT_CURVE_TAG(tags[0]);
float fpriX = int26p6_to_float(v_control.x);
float fpriY = -int26p6_to_float(v_control.y);
float startX = fpriX;
float startY = fpriY;
while(point < limit)
{
point++;
tags++;
tag = FT_CURVE_TAG(tags[0]);
switch(tag)
{
caseFT_CURVE_TAG_ON:
{
float fEndX = int26p6_to_float(point->x);
float fEndY = -int26p6_to_float(point->y);
QPen pen(RandColor());
painter.setPen(pen);
painter.drawLine(startX,startY,fEndX,fEndY);
startX= fEndX;
startY= fEndY;
}
break;
case FT_CURVE_TAG_CONIC: //二次Bezier曲线
{
v_control.x= point->x;
v_control.y= point->y;
Do_Conic:
if(point < limit)
{
FT_Vectorvec;
FT_Vectorv_middle;
point++;
tags++;
tag= FT_CURVE_TAG(tags[0]);
vec.x= point->x;
vec.y= point->y;
if(tag == FT_CURVE_TAG_ON)
{
float x1 = int26p6_to_float(v_control.x);
float y1 = -int26p6_to_float(v_control.y);
float x2 = int26p6_to_float(vec.x);
float y2 = -int26p6_to_float(vec.y);
QPen pen(RandColor());
painter.setPen(pen);
QPainterPathpath;
path.moveTo(startX,startY);
path.quadTo(x1,y1,x2,y2);
painter.drawPath(path);
startX= x2;
startY= y2;
continue;
}
if(tag != FT_CURVE_TAG_CONIC)
{
return;
}
v_middle.x= (v_control.x + vec.x) / 2;
v_middle.y= (v_control.y + vec.y) / 2;
float x1 = int26p6_to_float(v_control.x);
float y1 = -int26p6_to_float(v_control.y);
float x2 = int26p6_to_float(v_middle.x);
float y2 = -int26p6_to_float(v_middle.y);
QPenpen(RandColor());
painter.setPen(pen);
QPainterPathpath;
path.moveTo(startX,startY);
path.quadTo(x1,y1,x2,y2);
painter.drawPath(path);
startX= x2;
startY= y2;
v_control= vec;
goto Do_Conic;
}
float x1 = int26p6_to_float(v_control.x);
float y1 = -int26p6_to_float(v_control.y);
float x2 = int26p6_to_float(v_start.x);
float y2 = -int26p6_to_float(v_start.y);
QPenpen(RandColor());
painter.setPen(pen);
QPainterPathpath;
path.moveTo(startX,startY);
path.quadTo(x1,y1,x2,y2);
painter.drawPath(path);
startX= x2;
startY= y2;
goto Close;
}
break;
default: // FT_CURVE_TAG_CUBIC 三次Bezier曲线
{
FT_Vectorvec1, vec2;
if(point + 1 > limit ||FT_CURVE_TAG(tags[1]) != FT_CURVE_TAG_CUBIC)
{
return;
}
vec1.x= point[0].x;
vec1.y= point[0].y;
vec2.x= point[1].x;
vec2.y= point[1].y;
point+= 2;
tags += 2;
if(point <= limit)
{
FT_Vectorvec;
vec.x= point->x;
vec.y= point->y;
float x1 = int26p6_to_float(vec1.x);
float y1 = -int26p6_to_float(vec1.y);
float x2 = int26p6_to_float(vec2.x);
float y2 = -int26p6_to_float(vec2.y);
float x3 = int26p6_to_float(vec.x);
float y3 = -int26p6_to_float(vec.y);
QPenpen(RandColor());
painter.setPen(pen);
QPainterPathpath;
path.moveTo(startX,startY);
path.cubicTo(x1,y1,x2,y2,x3,y3);
painter.drawPath(path);
startX= x3;
startY= y3;
continue;
}
float x1 = int26p6_to_float(vec1.x);
float y1 = -int26p6_to_float(vec1.y);
float x2 = int26p6_to_float(vec2.x);
float y2 = -int26p6_to_float(vec2.y);
float x3 = int26p6_to_float(v_start.x);
float y3 = -int26p6_to_float(v_start.y);
QPenpen(QColor(255,0,0));
painter.setPen(pen);
QPainterPathpath;
path.moveTo(startX,startY);
path.cubicTo(x1,y1,x2,y2,x3,y3);
painter.drawPath(path);
startX= x3;
startY= y3;
goto Close;
}
}
}
Close:
QPenpen(QColor(255,0,0));
painter.setPen(pen);
painter.drawLine(startX,startY,fpriX,fpriY);
first= last + 1;
}
上面的解析代码中。并没有自己去计算二、三次Bezier曲线、而是使用了Qt库中,绘制Bezier曲线的方法。具体代码是: QPainterPath path;
path.moveTo(startX,startY);
path.cubicTo(x1,y1,x2,y2,x3,y3);
painter.drawPath(path);为了显示轮廓线的具体细节、也就是每个轮廓线的,一条条的曲线、在绘制的时候、每段小曲线段、使用了不同的颜色,这样就可以很清楚的看出一条条的Bezier曲线,或者直线段,同时也绘制了一张单一颜色的图像、两张图像如下。