基于Qt的FreeType字体轮廓解析

一、本文目的

以前的文档中、详细的介绍了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弧。

 

(二)、轮廓描述符
FT轮廓通过一个简单的结构描述

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曲线,或者直线段,同时也绘制了一张单一颜色的图像、两张图像如下。

基于Qt的FreeType字体轮廓解析_第1张图片基于Qt的FreeType字体轮廓解析_第2张图片

 

你可能感兴趣的:(C++,字体,qt)