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


     

  • 相关阅读:
    口腔溃疡
    English 好的报纸
    线段树
    归并排序 霍纳规则(法则) 统计逆序对
    xfce4桌面自动整理脚本
    解决xubuntu的thunar第一次启动慢
    解决ibus图标为红圈(图标丢失)
    linux tar 备份命令
    ubuntu中的Wine详解
    我的conky配置
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3318120.html
Copyright © 2020-2023  润新知