前言
最近在做一款激光打標控制的產品,我的思路是將所有的圖元矢量化,但是當做到文字矢量化的時候,真是讓我想破了腦袋,後來搜索得知了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字體庫初始化(省略了變量聲明)
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控制點後,就補償一箇中值。最後,我們’拆環’就可以了
解析算法描述
單個輪廓線首尾索引值獲取
首尾端點補償
按照上述規則即可,具體看附件工程
中值補償
也是按照上述規則
很多東西看代碼比較直接,附上大家最想要的完整工程,記得改一下pro文件裏面的lib目錄路徑:
戳我下載源碼