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


     

  • 相关阅读:
    diary and html 文本颜色编辑,行距和其它编辑总汇
    bash coding to changeNames
    virtualbox ubuntu 网络连接 以及 连接 secureCRT
    linux 学习6 软件包安装
    linux 学习8 权限管理
    vim 使用2 转载 为了打开方便
    ubuntu
    linux 学习15 16 启动管理,备份和恢复
    linux 学习 14 日志管理
    linux 学习 13 系统管理
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3318120.html
Copyright © 2011-2022 走看看