国内首篇利用freetype的跨平台truetype字体真正轮廓(非位图)获取

转载自:https://blog.csdn.net/tianyuan521521/article/details/46273407

前言
最近在做一款激光打标控制的产品,我的思路是将所有的图元矢量化,但是当做到文字矢量化的时候,真是让我想破了脑袋,后来搜索得知了GetGlyphOutline,就是一个WINAPI,众所周知,WINAPI大都需要一个HDC,但是我使用的Qt,这样就出现了几个问题:

Qt中的控件有些是直接渲染的,很难直接获取控件句柄-
不跨平台,以后这一块还是要重写
VC的各种变量类型写在Qt里面,调试起来真是一团糟
做了些实验以后,发现这种方法实在是缘木求鱼,然后轻易地搜索到了freeType这一款优秀的字体引擎,下面我们就开始对freetype的探索。

所需基本知识点
trueType字体的一些基本概念
字体(font):不同字符图像的集合

字体外观(font face):一个外观对应一个(.ttf)文件,但是一个字体家族可能占用多个字体文件,因为它包括多种外观,比如字体族Arial,它包括两种外观,于是就有 arial.ttf对应Arial Regular外观,ariali.ttf对应Arial Italic外观,我们习惯把Arial Regular也称为一种字体,实际上它只是一种字体外观

字体文件的基本结构:一般里面有一个或多个字符图(charmap),不同的字符图一般标识不同的平台,所以在一种平台上一般只有一种字符图,这个字符图可以宏观简单的理解为一个key-value,key-字符索引一般就是字符对应编码的编码值,value(字符构成)在truetype一般是对其矢量图形的描述,我们只要将字符编码对应的矢量图形描述得到,就可以进行随心所欲的处理了

trueType字体的基本构成
首现说一下字体轮廓,拿我自己解析出来的一个例子来说吧(这是俺的大名:)): 
 
渲染出来的矢量还是不错的! 
轮廓线就是字体轮廓中一条条的封闭曲线: 
 
红色箭头标注的就是字体轮廓线,共有两条;每条轮廓线又由其他直线或曲线组成:

直线
二次Bezier曲线
freetype官方说明字体曲线可能包含有(三次Bezier曲线),但经过我的实验以及其他官方资料,truetype字体并不含有三次Bezier曲线
besier曲线定义
由于此处只用到一次贝塞尔(有界直线)和二次贝塞尔曲线 
给定点P0、P1,线性bezier曲线只是一条两点之间的直线。这条线由下式给出,且其等同于线性插值。 
 
二次方bezier曲线的路径由给定点P0、P1、P2的函数B(t)追踪: 
 
按照上述插值规则,如果t分割的足够密,就可以得到平滑的曲线

freetype对trueType的解析
解析基本步骤
开始
freetype字体初始化
设置字符编码方式
获取字符对应的编码值
查charmap表获取编码值的索引
根据索引获取轮廓描述
对获取到的轮廓数据进行补偿
相关渲染和处理
结束
freetype字体库初始化(省略了变量声明)
int  error = FT_Init_FreeType( &library );
    if(error)
    {
        printf("load freetype errror!");
    }
    error = FT_New_Face(library,fontFilePath.toStdString().c_str() ,0,&face);
    if ( error == FT_Err_Unknown_File_Format )
        {
        printf("FT_Err_Unknown_File_Format");
    }
    else if(error)
    {
        printf(" another error code means that the font file could not  or simply that it is broken...");
    }
    error = FT_Set_Pixel_Sizes(face, /* handle to face object */
                                      0, /* pixel_width */
                                   8 );/* pixel_height */
    if(error)
    {
        printf("char size set error");
    }

设置字体编码方式
只需一句代码

FT_Select_Charmap(face,FT_ENCODING_UNICODE);

获取字符编码值
wchar_t charX= L'元';

对字体轮廓进行解析
这里我们需要先对FT_Outline这个freetype内置结构体做一个说明: 
FT_Outline

n_points:轮廓中的点数
n_contours 轮廓中轮廓线数
points 点坐标数组
contours 轮廓线端点索引数组
tags 点标记数组
注意:下方全程高能 
这里,points是一个FT_Vector记录数组的指针,用来存储每个轮廓点的向量坐标。它表示为一个象素1/64,也叫做26.6固定浮点格式。 
contours是一组点索引,用来划定轮廓的轮廓线。例如,第一个轮廓线总是从0点开始,以contours[0]点结束。第二个轮廓线从contours[0]+1点开始,以contours[1]结束,等等。 
注意,每条轮廓线都是封闭的,n_points应该和contours[n_controus-1]+1相同。最后,tags是一组字节,用来存放每个轮廓的点标记。 
从这儿,大家可能就会感觉以下的步骤很麻烦了!是的,至少会需要两层循环,这还不算完,最难处理的是下面这一堆规定:

轮廓内部点规则描述
每条弧由一系列起点、终点和控制点描述,轮廓的每个点有一个特定的标记,表示它用来描述一个线段还是一条弧。这个标记可以有以下值:
FT_Curve_Tag_On 当点在曲线上,这对应线段和弧的起点和终点。其他标记叫做“Off”点,即它不在轮廓线上,但是作为Bezier弧的控制点。
FT_Curve_Tag_Conic 一个Off点,控制一个conic Bezier弧
FT_Curve_Tag_Cubic 一个Off点,控制一个cubic Bezier弧
下面的规则应用于将轮廓点分解成线段和弧 z 两个相邻的“on”点表示一条线段;
弧z 一个conic Off(二次bezier曲线控制点)在两个on点之间表示一个conic Bezier(二次bezier)弧,off点是控制点,on点是起点和终点;
两个相邻的cubic off(三次bezier曲线控制点)点在两个on点之间表示一个cubic Bezier(三次bezier)弧,它必须有两个cubic控制点和两个on 
点。
两个相邻的conic off(二次bezier曲线控制点)强制在它们正中间创建一个虚拟的on点(坐标为两者中点)。这大大方便定义连续的conic弧。TrueType规范就是这么定义的。
轮廓端点规则描述
如果轮廓的首尾点均为FT_Curve_Tag_On,则不需做任何处理
如果轮廓首点为FT_Curve_Tag_Conic,尾点为FT_Curve_Tag_On,就将尾点作为第一条bezier曲线的首点
如果轮廓首点为FT_Curve_Tag_On,尾点为FT_Curve_Tag_Conic,就将首点作为最后一条bezier曲线的尾点
如果首尾点均为FT_Curve_Tag_Conic,则取两者平均值分别作为第一条bezier曲线的首点和最后一条曲线的尾点。
规则总结
这一串规则看起来非常复杂晦涩,其实总结一下就是,每个轮廓点就是是一个’圆环’的点集,当出现相邻的bezier控制点后,就补偿一个中值。最后,我们’拆环’就可以了

解析算法描述
获取outline中的各个值
单个轮廓线首尾索引值获取
首端点补偿
中点连续的控制点的中值补偿
尾端点补偿
拆分成一段段独立bezier曲线
结束
单个轮廓线首尾索引值获取
单个轮廓线首尾索引值获取
第几条轮廓线
是否是第0条 ?
索引值 0~outline->contours[0]
处理完毕
索引值 outline->contours[contourIndex -1] +1~outline->contours[contourIndex]
yes
no
首尾端点补偿
按照上述规则即可,具体看附件工程
1
中值补偿
也是按照上述规则

很多东西看代码比较直接,附上大家最想要的完整工程,记得改一下pro文件里面的lib目录路径: 
 

发布了54 篇原创文章 · 获赞 89 · 访问量 68万+

猜你喜欢

转载自blog.csdn.net/ayang1986/article/details/96484669