這是「FreeType2 教程」的第二部分。它將教會你如何html
* 檢索字形度量
* 便捷地管理字形圖像
* 檢索全局度量(包括字距調整)
* 渲染一個簡單的字符串(採用字距調整)
* 渲染一個居中的字符串(採用字距調整)
* 渲染一個經變換的字符串(採用居中)
* 在須要時以預設字體單位的格式獲取度量,以及把它們縮放到設備空間數組
1.字形度量緩存
顧名思義,字形度量是對應每個字形的特定距離,以此描述如何對文本排版。
一般一個字形有兩個度量集:用來排版水平文本排列的字形(拉丁文、西里爾文、阿拉伯文、希伯來文等等)和用來排版垂直文本排列的字形(中文、日文、韓文等等)。
要注意的是隻有不多的字體格式提供了垂直度量。你可使用宏FT_HAS_VERTICAL測試某個給出的face對象是否包含垂直度量,當結果爲真時表示包含垂直度量。
每一個的字形度量均可以先裝載字形到face的字形槽,而後經過face->glyph->metrics結構訪問,其類型爲FT_Glyph_Metrics。咱們將在下面詳細討論它,如今,咱們只關注該結構包含以下的字段:函數
Width
這是字形圖像的邊框的寬度。它與排列方向無關。
Height
這是字形圖像的邊框的高度。它與排列方向無關。千萬不要把它和FT_Size_Metrics的height字段混淆。
horiBearingX
用於水平文本排列,這是從當前光標位置到字形圖像最左邊的邊界的水平距離。
horiBearingY
用於水平文本排列,這是從當前光標位置(位於基線)到字形圖像最上邊的邊界的水平距離。 horiAdvance
用於水平文本排列,當字形做爲字符串的一部分被繪製時,這用來增長筆位置的水平距離。
vertBearingX
用於垂直文本排列,這是從當前光標位置到字形圖像最左邊的邊框的垂直距離。
vertBearingY
用於垂直文本排列,這是從當前光標位置(位於基線)到字形圖像最上邊的邊框的垂直距離。 vertAdvance
用於垂直文本排列,當字形做爲字符串的一部分被繪製時,這用來增長筆位置的垂直距離。
注意:由於不是全部的字體都包含垂直度量,當FT_HAS_VERTICAL爲假時,vertBearingX,vertBearingY和vertAdvance的值是不可靠的。佈局
下面的圖形更清楚地圖解了度量。第一個圖解了水平度量,其基線爲水平軸:測試
對於垂直文本排列,基線是垂直的,與垂直軸一致:字體
Face->glyph->metrics中的度量一般以26.6象素格式(例如1/64象素)表示,除非你在調用FT_Load_Glyph或FT_Load_Char時使用了FT_LOAD_NO_SCALE標誌,這樣的話度量會用原始字體單位表示。
字形槽(glyph slot)對象也有一些其餘有趣的字段能夠減輕開發者的工做。你能夠經過face->glyph->xxx訪問它們,其中xxx是下面字段之一:spa
Advance
這個字段是一個FT_Vector,保存字形的經變換步長。當你經過FT_Set_Transform使用變換時,這是頗有用的,這在第一部分的循環文本例子中已經展現過了。這個值是默認的(metrics.horiAdvance,0),除非你在裝載字形圖像時指定FT_LOAD_VERTICAL,那麼它將會爲(0,metrics.vertAdvance),這點與第一部分的例子不一樣。
linearHoriAdvance
這個字段包含字形水平推動寬度的線性刻度值。實際上,字形槽返回的metrics.horiAdvance值一般四捨五入爲整數象素座標(例如,它是64的倍數),字體驅動器用它裝載字形圖像。linearHoriAdvance是一個16.16固定浮點數,提供了以1/65536象素爲單位的原始字形推動寬度的值。它能夠用來完成僞設備無關文字排版。
linearVertAdvance
這與linearHoriAdvance相似,但它用於字形的垂直推動高度。只有當字體face包含垂直度量時這個值纔是可靠的。.net
裝載到字形槽的字形能夠轉換到一幅字形位圖中,這能夠在裝載時使用FT_LOAD_RENDER標誌或者調用FT_Render_Glyph函數實現。每一次你裝載一個新的字形到字形槽,前面裝載的字形將會從字形槽中抹去。
可是,你可能須要從字形槽中提取這個字形,並在你的應用程序中緩存它,或者進行附加的變換,或者在轉換成位圖前測量它。
FreeType 2 API有一個特殊的擴展可以以一種靈活和普通的方式處理字形圖像。要使用它,你首先須要包含FT_GLYPH_H頭文件,以下:設計
#include FT_GLYPH_H
如今咱們將解釋如何使用這個文件定義的這個函數。
a.提取字形圖像
你能夠很簡單地提取一個字形圖像。這裏有一貫代碼向你展現如何去作:
FT_Glyph glyph; ... error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NORMAL ); if ( error ) { ... } error = FT_Get_Glyph( face->glyph, &glyph ); if ( error ) { ... }
顯而易見,咱們:
* 建立一個類型爲FT_Glyph,名爲glyph的變量。這是一個字形圖像的句柄(即指針)。
* 裝載字形圖像(一般狀況下)到face的字形槽中。咱們不使用FT_LOAD_RENDER由於咱們想抓取一個可縮放的字形圖像,以便後面對其進行變換。
* 經過調用FT_Get_Glyph,把字形圖像從字形槽複製到新的FT_Glyph對象glyph中。這個函數返回一個錯誤碼而且設置glyph。
要很是留意,被取出的字形跟字形槽中的原始字形的格式是同樣的。例如,若是咱們從TrueType字體文件中裝載一個字形,字形圖像將是可伸縮的矢量輪廓。
若是你想知道字形是如何建模和存儲的,你能夠訪問flyph->format字段。一個字形對象能夠經過調用FT_Done_Glyph來銷燬。
字形對象正好包含一個字形圖像和一個2D矢量,2D矢量以16.16固定浮點座標的形式表示字形的推動步長。後者能夠直接經過glyph->advance訪問它。
注意,不一樣於其餘TrueType對象,庫不保存所有分配了的字形對象的列表。這意味着你必須本身銷燬它們,而不是依靠FT_Done_FreeType完成所有的清除。
b.變換和複製字形圖像
若是字形圖像是可伸縮的(glyph->format不等於FT_GLYPH_FORMAT_BITMAP),那麼就能夠隨時經過調用FT_Glyph_Transform來變換該圖像。
你也能夠經過FT_Glyph_Copy複製一個字形圖像。這裏是一些例子代碼:
FT_Glyph glyph, glyph2;
FT_Matrix matrix;
FT_Vector delta;
... 裝載字形圖像到 `glyph' ...
error = FT_Glyph_Copy( glyph, &glyph2 );
if ( error ) { ... 沒法複製(內存不足) ... }
delta.x = -100 * 64;
delta.y = 50 * 64;
FT_Glyph_Transform( glyph, 0, &delta );
matrix.xx = 0x10000L;
matrix.xy = 0.12 * 0x10000L;
matrix.yx = 0;
matrix.yy = 0x10000L;
FT_Glyph_Transform( glyph2, &matrix, 0 );
注意,2×2矩陣變換老是適用於字形的16.16推動步長矢量,因此你不須要重修計算它。
c.測量字形圖像
你也能夠經過FT_Glyph_Get_CBox函數檢索任意字形圖像(不管是可伸縮或者不可伸縮的)的控制(約束)框,以下:
FT_BBox bbox; ... FT_Glyph_Get_CBox( glyph, bbox_mode, &bbox );
座標是跟字形的原點(0, 0)相關的,使用y向上的約定。這個函數取一個特殊的參數:bbox_mode來指出如何表示框座標。
若是字形裝載時使用了FT_LOAD_NO_SCALE標誌,bbox_mode必須設置爲FT_GLYPH_BBOX_UNSCALED,以此來得到以26.6象素格式爲單位表示的不可縮放字體。值FT_GLYPH_BBOX_SUBPIXELS是這個常量的另外一個名字。
要注意,框(box)的最大座標是惟一的,這意味着你老是能夠以整數或26.6象素的形式計算字形圖像的寬度和高度,公式以下:
width = bbox.xMax - bbox.xMin; height = bbox.yMax - bbox.yMin;
同時要注意,對於26.6座標,若是FT_GLYPH_BBOX_GRIDFIT被用做爲bbox_mode,座標也將網格對齊,符合以下公式:
bbox.xMin = FLOOR( bbox.xMin ) bbox.yMin = FLOOR( bbox.yMin ) bbox.xMax = CEILING( bbox.xMax ) bbox.yMax = CEILING( bbox.yMax )
要把bbox以整數象素座標的形式表示,把bbox_mode設置爲FT_GLYPH_BBOX_TRUNCATE。
最後,要把約束框以網格對齊象素座標的形式表示,把bbox_mode設置爲FT_GLYPH_BBOX_PIXELS。
d.轉換字形圖像爲位圖
當你已經把字形對象緩存或者變換後,你可能須要轉換它到一個位圖。這能夠經過FT_Glyph_To_Bitmap函數簡單得實現。它負責轉換任何字形對象到位圖,以下:
FT_Vector origin; origin.x = 32; origin.y = 0; error = FT_Glyph_To_Bitmap( &glyph, render_mode, &origin, 1 );
一些註解:
* 第一個參數是源字形句柄的地址。當這個函數被調用時,它讀取該參數來訪問源字形對象。調用結束後,這個句柄將指向一個新的包含渲染後的位圖的字形對象。
* 第二個參數時一個標準渲染模式,用來指定咱們想要哪一種位圖。它取FT_RENDER_MODE_DEFAULT時表示8位顏色深度的抗鋸齒位圖;它取FT_RENDER_MODE_MONO時表示1位顏色深度的黑白位圖。
* 第三個參數是二維矢量的指針。該二維矢量是用來在轉換前平移源字形圖像的。要注意,函數調用後源圖像將被平移回它的原始位置(這樣源圖像便不會有變化)。若是你在渲染前不須要平移源字形,設置這個指針爲0。
* 最後一個參數是一個布爾值,用來指示該函數是否要銷燬源字形對象。若是爲false,源字形對象不會被銷燬,可是它的句柄丟失了(客戶應用程序須要本身保留句柄)。
若是沒返回錯誤,新的字形對象老是包含一個位圖。而且你必須把它的句柄進行強制類型轉換,轉換爲FT_BitmapGlyph類型,以此訪問它的內容。這個類型是FT_Glyph的一種「子類」,它包含下面的附加字段(看FT_BitmapGlyphRec):
Left
相似於字形槽的bitmap_left字段。這是字形原點(0,0)到字形位圖最左邊象素的水平距離。它以整數象素的形式表示。
Top
相似於字形槽的bitmap_top字段。它是字形原點(0,0)到字形位圖最高象素之間的垂直距離(更精確來講,到位圖最上面的象素)。這個距離以整數象素的形式表示,而且y軸向上爲正。Bitmap
這是一個字形對象的位圖描述符,就像字形槽的bitmap字段。
不一樣於字形度量,全局度量是用來描述整個字體face的距離和輪廓的。他們能夠用26.6象素格式或者可縮放格式的「字體單位」來表示。
a.預設全局度量
對於可縮放格式,所有全局度量都是以字體單位的格式表示的,這能夠用來在稍後依照本教程本部分的最後一章描述的規則來縮放到設備空間。你能夠經過FT_Face句柄的字段直接訪問它們。
然而,你須要在使用它們前檢查字體face的格式是否可縮放。你可使用宏FT_IS_SCALEABLE來實現,當該字體是可縮放時它返回正。
若是是這樣,你就能夠訪問全局預設度量了,以下:
units_per_EM
這是字體face的EM正方形的大小。它是可縮放格式用來縮放預設座標到設備象素的,咱們在這部分的最後一章敘述它。一般這個值爲2048(對於TrueType)或者1000(對於Type 1),可是其餘值也是可能的。對於固定尺寸格式,如FNT/FON/PCF/BDF,它的值爲1。
global_bbox
全局約束框被定義爲最大矩形,該矩形能夠包圍字體face的全部字形。它只爲水平排版而定義。
ascender
Ascender是從水平基線到字體face最高「字符」的座標之間的垂直距離。不幸地,不一樣的字體格式對ascender的定義是不一樣的。對於某些來講,它表明了所有大寫拉丁字符(重音符合除外)的上沿值(ascent);對於其餘,它表明了最高的重音符號的上沿值(ascent);最後,其餘格式把它定義爲跟global_bbox.yMax相同。
descender
Descender是從水平基線到字體face最低「字符」的座標之間的垂直距離。不幸地,不一樣的字體格式對descender的定義是不一樣的。對於某些來講,它表明了所有大寫拉丁字符(重音符合除外)的下沿值(descent);對於其餘,它表明了最高的重音符號的下沿值(descent);最後,其餘格式把它定義爲跟global_bbox.yMin相同。這個字段的值是負數。
text_height
這個字段是在使用這個字體書寫文本時用來計算默認的行距的(例如,基線到基線之間的距離)。注意,一般它都比ascender和descent的絕對值之和還要大。另外,不保證使用這個距離後面就沒有字形高於或低於基線。
max_advance_width
這個字段指出了字體中全部字形得最大的水平光標推動寬度。它能夠用來快速計算字符串得最大推動寬度。它不等於最大字形圖像寬度!
max_advance_height
跟max_advance_width同樣,可是用在垂直文本排版。它只在字體提供垂直字形度量時纔可用。
underline_position
當顯示或者渲染下劃線文本時,這個值等於下劃線到基線的垂直距離。當下劃線低於基線時這個值爲負數。
underline_thickness
當顯示或者渲染下劃線文本時,這個值等於下劃線的垂直寬度。
如今注意,很不幸的,因爲字體格式多種多樣,ascender和descender的值是不可靠的。
b.伸縮的全局度量
每個size對象同時包含了上面描述的某些全局度量的伸縮版本。它們能夠經過face->size->metrics結構直接訪問。
注意這些值等於預設全局變量的伸縮版本,但沒有作舍入或網格對齊。它們也徹底獨立於任何hinting處理。換句話說,不要依靠它們來獲取象素級別的精確度量。它們以26.6象素格式表示。
ascender
原始預設ascender的伸縮版本。
descender
原始預設ascender的伸縮版本。
height
原始預設文本高度(text_height)的伸縮版本。這多是這個結構中你真正會用到的字段。
max_advance
原始預設最大推動的伸縮版本。
注意,face->size->metrics結構還包含其餘字段,用來伸縮預設座標到設備空間。它們會在最後一章描述。
c.字距調整
字距調整是調整字符串中兩個並排的字形圖像位置的過程,它能夠改善文本的總體外觀。基本上,這意味着當‘A’的跟着‘V’時,它們之間的間距能夠稍微減小,以此避免額外的「對角線空白」。
注意,理論上字距調整適用於水平和垂直方向的兩個字形,可是,除了很是極端的狀況外,幾乎在全部狀況下,它只會發生在水平方向。
不是全部的字體格式包含字距調整信息。有時候它們依賴於一個附加的文件來保存不一樣的字形度量,包括字距調整,但該文件不包含字形圖像。一個顯著的例子就是Type1格式。它的字形圖像保存在一個擴展名爲.pfa或.pfb的文件中,字距調整度量存放在一個附加的擴展名爲.afm或.pfm的文件中。
FreeType 2提供了FT_Attach_File和FT_Attach_Stream API來讓你處理這種狀況。兩個函數都是用來裝載附加的度量到一個face對象中,它經過從附加的特定格式文件中讀取字距調整度量來實現。例如,你能夠象下面那樣打開一個Type1字體:
error = FT_New_Face( library, "/usr/shared/fonts/cour.pfb", 0, &face ); if ( error ) { ... } error = FT_Attach_File( face, "/usr/shared/fonts/cour.afm" ); if ( error ) { ... 沒能讀取字距調整和附加的度量 ... }
注意,FT_Attach_Stream跟FT_Attach_File是相似的,不一樣的是它不是以C字符串指定附加文件,而是以一個FT_Stream句柄。另外,讀取一個度量文件不是強制性的。
最後,文件附加API是很是通用的,能夠用來從指定的face中裝載不一樣類型的附加信息。附加內容的種類徹底是因字體格式而異的。
FreeType 2容許你經過FT_Get_Kerning函數獲取兩個字形的字距調整信息,該函數界面以下:
FT_Vector kerning; ... error = FT_Get_Kerning( face, left, right, kerning_mode, &kerning );
正如你所見到的,這個函數的參數有一個face對象的句柄、字距調整值所要求的左邊和右邊字形索引,以及一個稱爲字距調整模式的整數,和目標矢量的指針。目標矢量返回適合的距離值。
字距調整模式跟前一章描述的bbox模式(bbox mode)是很相似的。這是一個枚舉值,指示了目標矢量如何表示字距調整距離。
默認值是FT_KERNING_DEFAULT,其數值爲0。它指示字距調整距離以26.6網格對齊象素(這意味着該值是64的倍數)的形式表示。對於可伸縮格式,這意味着返回值是把預設字距調整距離先伸縮,而後舍入。
值FT_KERNING_UNFITTED指示了字距調整距離以26.6非對齊象素(也就是,那不符合整數座標)的形式表示。返回值是把預設字距調整伸縮,但不捨入。
最後,值FT_KERNING_UNSCALED是用來返回預設字距調整距離,它以字體單位的格式表示。你能夠在稍後用本部分的最後一章描述的算式把它拉伸到設備空間。
注意,「左」和「右」位置是指字符串字形的可視順序。這對雙向或由右到左的文原本說是很重要的。
爲了顯示咱們剛剛學到的知識,如今咱們將示範如何修改第一部分給出的代碼以渲染一個字符串,而且加強它,使它支持字距調整和延遲渲染。
a.字距調整支持
要是咱們只考慮處理從左到右的文字,如拉丁文,那在咱們的代碼上添加字距調整是很容易辦到的。咱們只要獲取兩個字形之間的字距調整距離,而後適當地改變筆位置。代碼以下:
FT_GlyphSlot slot = face->glyph; FT_UInt glyph_index; FT_Bool use_kerning; FT_UInt previous; int pen_x, pen_y, n; ... 初始化庫 ... ... 建立face對象 ... ... 設置字符尺寸 ... pen_x = 300; pen_y = 200; use_kerning = FT_HAS_KERNING( face ); previous = 0; for ( n = 0; n < num_chars; n++ ) { glyph_index = FT_Get_Char_Index( face, text[n] ); if ( use_kerning && previous && glyph_index ) { FT_Vector delta; FT_Get_Kerning( face, previous, glyph_index, ft_kerning_mode_default, &delta ); pen_x += delta.x >> 6; } Error = FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER); if ( error ) continue; my_draw_bitmap( &slot->bitmap, pen_x + slot->bitmap_left, pen_y - slot->bitmap_top ); pen_x += slot->advance.x >> 6; previous = glyph_index; }
若干註解:
* 由於字距調整是由字形索引決定的,咱們須要顯式轉換咱們的字符代碼到字形索引,而後調用FT_Load_Glyph而不是FT_Load_Char。
* 咱們使用一個名爲use_kerning的變量,它的值爲宏FT_HAS_KERNING的結果。當咱們知道字體face不含有字距調整信息,不調用FT_Get_kerning程序將執行得更快。
* 咱們在繪製一個新字形前移動筆位置。
* 咱們以值0初始化變量previous,這表示「字形缺失(missing glyph)」(在Postscript中,這用.notdef表示)。該字形也沒有字距調整距離。
* 咱們不檢查FT_Get_kerning返回得錯誤碼。這是由於這個函數在錯誤發生時老是把delta置爲(0,0)。
b.居中
咱們的代碼開始變得有趣了,但對普通應用來講仍然有點太簡單了。例如,筆的位置在咱們渲染前就決定了。一般,你要在計算文本的最終位置(居中,等)前佈局它和測量它,或者執行自動換行。
如今讓咱們把文字渲染函數分解爲兩個大相徑庭但連續的兩部分:第一部分將在基線上定位每個字形圖像,第二部分將渲染字形。咱們將看到,這有不少好處。
咱們先保存每個獨立的字形圖像,以及它們在基線上面的位置。這能夠經過以下的代碼完成:
FT_GlyphSlot slot = face->glyph; FT_UInt glyph_index; FT_Bool use_kerning; FT_UInt previous; int pen_x, pen_y, n; FT_Glyph glyphs[MAX_GLYPHS]; FT_Vector pos [MAX_GLYPHS]; FT_UInt num_glyphs; ... 初始化庫 ... ... 建立face對象 ... ... 設置字符尺寸 ... pen_x = 0; pen_y = 0; num_glyphs = 0; use_kerning = FT_HAS_KERNING( face ); previous = 0; for ( n = 0; n < num_chars; n++ ) { glyph_index = FT_Get_Char_Index( face, text[n] ); if ( use_kerning && previous && glyph_index ) { FT_Vector delta; FT_Get_Kerning( face, previous, glyph_index, FT_KERNING_DEFAULT, &delta ); pen_x += delta.x >> 6; } pos[num_glyphs].x = pen_x; pos[num_glyphs].y = pen_y; error=FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); if ( error ) continue; error = FT_Get_Glyph( face->glyph, &glyphs[num_glyphs] ); if ( error ) continue; pen_x += slot->advance.x >> 6; previous = glyph_index; num_glyphs++; }
相對於咱們以前的代碼,這有輕微的變化:咱們從字形槽中提取每個字形圖像,保存每個字形圖像和它對應的位置在咱們的表中。
注意pen_x包含字符串的總體前移值。如今咱們能夠用一個很簡單的函數計算字符串的邊界框(bounding box),以下:
void compute_string_bbox( FT_BBox *abbox ) { FT_BBox bbox; bbox.xMin = bbox.yMin = 32000; bbox.xMax = bbox.yMax = -32000; for ( n = 0; n < num_glyphs; n++ ) { FT_BBox glyph_bbox; FT_Glyph_Get_CBox( glyphs[n], ft_glyph_bbox_pixels, &glyph_bbox ); glyph_bbox.xMin += pos[n].x; glyph_bbox.xMax += pos[n].x; glyph_bbox.yMin += pos[n].y; glyph_bbox.yMax += pos[n].y; if ( glyph_bbox.xMin < bbox.xMin ) bbox.xMin = glyph_bbox.xMin; if ( glyph_bbox.yMin < bbox.yMin ) bbox.yMin = glyph_bbox.yMin; if ( glyph_bbox.xMax > bbox.xMax ) bbox.xMax = glyph_bbox.xMax; if ( glyph_bbox.yMax > bbox.yMax ) bbox.yMax = glyph_bbox.yMax; } if ( bbox.xMin > bbox.xMax ) { bbox.xMin = 0; bbox.yMin = 0; bbox.xMax = 0; bbox.yMax = 0; } *abbox = bbox; }
最終獲得的邊界框尺寸以整數象素的格式表示,而且能夠隨後在渲染字符串前用來計算最終的筆位置,以下:
string_width = string_bbox.xMax - string_bbox.xMin; string_height = string_bbox.yMax - string_bbox.yMin; start_x = ( ( my_target_width - string_width ) / 2 ) * 64; start_y = ( ( my_target_height - string_height ) / 2 ) * 64; for ( n = 0; n < num_glyphs; n++ ) { FT_Glyph image; FT_Vector pen; image = glyphs[n]; pen.x = start_x + pos[n].x; pen.y = start_y + pos[n].y; error = FT_Glyph_To_Bitmap(&image, FT_RENDER_MODE_NORMAL, &pen, 0 ); if ( !error ) { FT_BitmapGlyph bit = (FT_BitmapGlyph)image; my_draw_bitmap( bit->bitmap, bit->left, my_target_height - bit->top ); FT_Done_Glyph( image ); } }
一些說明:
* 筆位置以笛卡兒空間(例如,y向上)的形式表示。
* 咱們調用FT_Glyph_To_Bitmap時destroy參數設置爲0(false),這是爲了不破壞原始字形圖像。在執行該調用後,新的字形位圖經過image訪問,而且它的類型轉變爲FT_BitmapGlyph。
* 當調用FT_Glyph_To_Bitmap時,咱們使用了平移。這能夠確保位圖字形對象的左區域和上區域已經被設置爲笛卡兒空間中的正確的象素座標。
* 固然,在渲染前咱們仍然須要把象素座標從笛卡兒空間轉換到設備空間。所以在調用my_draw_bitmap前要先計算my_target_height – bitmap->top。
相同的循環能夠用來把字符串渲染到咱們的顯示面(surface)任意位置,而不須要每一次都從新裝載咱們的字形圖像。咱們也能夠決定實現自動換行或者只是繪製。
5.高級文本渲染:變換 + 居中 + 字距調整
如今咱們將修改咱們的代碼,以即可以容易地變換已渲染的字符串,例如旋轉它。咱們將以實行少量小改進開始:
a.打包而後平移字形
咱們先把與一個字形圖像相關的信息打包到一個結構體,而不是並行的數組。所以咱們定義下面的結構體類型:
typedef struct TGlyph_ { FT_UInt index; FT_Vector pos; FT_Glyph image; } TGlyph, *PGlyph;
咱們在裝載每個字形圖像過程當中,在把它裝載它在基線所在位置後便直接平移它。咱們將看到,這有若干好處。咱們的字形序列裝載其於是變成:
FT_GlyphSlot slot = face->glyph; FT_UInt glyph_index; FT_Bool use_kerning; FT_UInt previous; int pen_x, pen_y, n; TGlyph glyphs[MAX_GLYPHS]; PGlyph glyph; FT_UInt num_glyphs; ... 初始化庫 ... ... 建立face對象 ... ... 設置字符尺寸 ... pen_x = 0; pen_y = 0; num_glyphs = 0; use_kerning = FT_HAS_KERNING( face ); previous = 0; glyph = glyphs; for ( n = 0; n < num_chars; n++ ) { glyph->index = FT_Get_Char_Index( face, text[n] ); if ( use_kerning && previous && glyph->index ) { FT_Vector delta; FT_Get_Kerning( face, previous, glyph->index, FT_KERNING_MODE_DEFAULT, &delta ); pen_x += delta.x >> 6; } glyph->pos.x = pen_x; glyph->pos.y = pen_y; error = FT_Load_Glyph(face,glyph_index,FT_LOAD_DEFAULT); if ( error ) continue; error = FT_Get_Glyph( face->glyph, &glyph->image ); if ( error ) continue; FT_Glyph_Transform( glyph->image, 0, &glyph->pos ); pen_x += slot->advance.x >> 6; previous = glyph->index; glyph++; } num_glyphs = glyph - glyphs;
注意,這個時候平移字形有若干好處。第一是當咱們計算字符串的邊界框時不須要平移字形bbox。代碼將會變成這樣:
void compute_string_bbox( FT_BBox *abbox ) { FT_BBox bbox; bbox.xMin = bbox.yMin = 32000; bbox.xMax = bbox.yMax = -32000; for ( n = 0; n < num_glyphs; n++ ) { FT_BBox glyph_bbox; FT_Glyph_Get_CBox( glyphs[n], &glyph_bbox ); if (glyph_bbox.xMin < bbox.xMin) bbox.xMin = glyph_bbox.xMin; if (glyph_bbox.yMin < bbox.yMin) bbox.yMin = glyph_bbox.yMin; if (glyph_bbox.xMax > bbox.xMax) bbox.xMax = glyph_bbox.xMax; if (glyph_bbox.yMax > bbox.yMax) bbox.yMax = glyph_bbox.yMax; } if ( bbox.xMin > bbox.xMax ) { bbox.xMin = 0; bbox.yMin = 0; bbox.xMax = 0; bbox.yMax = 0; } *abbox = bbox; }
更詳細描述:compute_string_bbox函數如今能夠計算一個已轉換的字形字符串的邊界框。例如,咱們能夠作以下的事情:
FT_BBox bbox; FT_Matrix matrix; FT_Vector delta; ... 裝載字形序列 ... ... 設置 "matrix" 和 "delta" ... for ( n = 0; n < num_glyphs; n++ ) FT_Glyph_Transform( glyphs[n].image, &matrix, &delta ); compute_string_bbox( &bbox );
b.渲染一個已變換的字形序列
不管如何,若是咱們想重用字形來以不一樣的角度或變換方式繪製字符串,直接變換序列中的字形都不是一個好主意。更好的方法是在字形被渲染前執行放射變換,以下面的代碼所示:
FT_Vector start; FT_Matrix transform; compute_string_bbox( &string_bbox ); string_width = (string_bbox.xMax - string_bbox.xMin) / 64; string_height = (string_bbox.yMax - string_bbox.yMin) / 64; start.x = ( ( my_target_width - string_width ) / 2 ) * 64; start.y = ( ( my_target_height - string_height ) / 2 ) * 64; matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L ); matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L ); matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L ); matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L ); for ( n = 0; n < num_glyphs; n++ ) { FT_Glyph image; FT_Vector pen; FT_BBox bbox; error = FT_Glyph_Copy( glyphs[n].image, &image ); if ( error ) continue; FT_Glyph_Transform( image, &matrix, &start ); FT_Glyph_Get_CBox( image, ft_glyph_bbox_pixels, &bbox ); if ( bbox.xMax <= 0 || bbox.xMin >= my_target_width || bbox.yMax <= 0 || bbox.yMin >= my_target_height ) continue; error = FT_Glyph_To_Bitmap( &image, FT_RENDER_MODE_NORMAL, 0, 1 ); if ( !error ) { FT_BitmapGlyph bit = (FT_BitmapGlyph)image; my_draw_bitmap( bitmap->bitmap, bitmap->left, my_target_height - bitmap->top ); FT_Done_Glyph( image ); } }
這份代碼相對於原始版本有少量改變:
* 咱們沒改變原始的字形圖像,而是變換該字形圖像的拷貝。
* 咱們執行「剪取」操做以處理渲染和繪製的字形不在咱們的目標表面(surface)的狀況。
* 當調用FT_Glyhp_To_Bitmap時,咱們老是銷燬字形圖像的拷貝,這是爲了銷燬已變換的圖像。注意,即便當這個函數返回錯誤碼,該圖像依然會被銷燬(這就是爲何FT_Done_Glyph只在複合語句中被調用的緣由)。
* 平移字形序列到起始筆位置集成到FT_Glyph_Transform函數,而不是FT_Glyph_To_Bitmap函數。
能夠屢次調用這個函數以渲染字符串到不一樣角度的,或者甚至改變計算start的方法以移動它到另外的地方。
這份代碼是FreeType 2示範程序ftstring.c的基礎。它能夠被簡單地擴展,在第一部發完成高級文本佈局或自動換行,而第二部分不需改變。
不管如何,要注意一般的實現會使用一個字形緩衝以減小內存消耗。據個例子,讓咱們假定咱們的字符串是「FreeType」。咱們將在咱們的表中保存字母‘e’的三個相同的字形圖像,這不是最佳的(特別是當你遇到更長的字符串或整個頁面時)。
6.以預設字體單位的格式訪問度量,而且伸縮它們
可伸縮的字體格式一般會爲字體face中的每個字形保存一份矢量圖像,該矢量圖像稱爲輪廓。每個輪廓都定義在一個抽象的網格中,該網格被稱爲預設空間(design space),其座標以名義上(nominal)的字體單位(font unit)表示。當裝載一個字形圖像時,字體驅動器一般會依照FT_Size對象所指定的當前字符象素尺寸把輪廓伸縮到設備空間。字體驅動器也能修改伸縮過的輪廓以大大地改善它在基於象素的表面(surface)中顯示的效果。修改動做一般稱爲hinting或網格對齊。
這一章描述瞭如何把預設座標伸縮到設備空間,以及如何讀取字形輪廓和如何獲取以預設字體單位格式表示的度量。這對許多事情來講都是重要的:
* 真正的所見即所得文字排版
* 爲了字體轉換或者分析的目的而訪問字體內容
a.伸縮距離到設備空間
咱們使用一個簡單的伸縮變換把預設座標伸縮到設備空間。變換系數借助字符象素尺寸來計算:
Device_x = design_x * x_scale Device_y = design_y * y_scale X_scale = pixel_size_x / EM_size Y_scale = pixel_size_y / EM_size
這裏,值EM_Size是因字體而異的,而且對應預設空間的一個抽象矩形(稱爲EM)的大小。字體設計者使用該矩形建立字形圖像。EM_Size以字體單元的形式表示。對於可伸縮字體格式,能夠經過face->unix_per_EM直接訪問。你應該使用FT_IS_SCALABLE宏檢查某個字體face是否包含可伸縮字形圖像,當包含時該宏返回true。
當你調用函數FT_Set_Pixel_Sizes,你便指定了pixel_size_x和pixel_size_y的值。FreeType庫將會當即使用該值計算x_scale和y_scale的值。
當你調用函數FT_Set_Char_Size,你便以物理點的形式指定了字符尺寸。FreeType庫將會使用該值和設備的解析度來計算字符象素尺寸和相應的比例因子。
注意,在調用上面說起的兩個函數後,你能夠經過訪問face->size->metrices結構的字段獲得字符象素尺寸和比例因子的值。這些字段是:
X_ppem
這個字段表明了「每個EM的x方向象素」,這是以整數象素表示EM矩形的水平尺寸,也是字符水平象素尺寸,即上面例子所稱的pixel_size_x。
y_ppem
這個字段表明了「每個EM的y方向象素」,這是以整數象素表示EM矩形的垂直尺寸,也是字符垂直象素尺寸,即上面例子所稱的pixel_size_y。
X_scale
這是一個16.16固定浮點比例,用來把水平距離從預設空間直接伸縮到1/64設備象素。
y_scale
這是一個16.16固定浮點比例,用來把垂直距離從預設空間直接伸縮到1/64設備象素。
你能夠藉助FT_MulFix函數直接伸縮一個以26.6象素格式表示的距離,以下所示:
pixels_x=FT_MulFix(design_x,face->size->metrics.x_scale); pixels_y=FT_MulFix(design_y,face->size->metrics.y_scale); 固然,你也可使用雙精度浮點數更精確地伸縮該值: FT_Size_Metrics* metrics = &face->size->metrics; double pixels_x, pixels_y; double em_size, x_scale, y_scale; em_size = 1.0 * face->units_per_EM; x_scale = metrics->x_ppem / em_size; y_scale = metrics->y_ppem / em_size; pixels_x = design_x * x_scale; pixels_y = design_y * y_scale;
b.訪問預設度量(字形的和全局的)
你能夠以字體單位的格式訪問字形度量,只要在調用FT_Load_Glyph或FT_Load_Char時簡單地指定FT_LOAD_NO_SCALE位標誌即可以了。度量返回在face->glyph_metrics,而且所有都以字體單位的格式表示。
你可使用FT_KERNING_MODE_UNSCALED模式訪問未伸縮的字距調整數據。
最後,FT_Face句柄的字段包含少數幾個全局度量,咱們已經在本部分的第三章敘述過了。
結論
這是FreeType 2教程第二部分的結尾。如今你能夠訪問字形度量,管理字形圖像,以及更巧妙地渲染文字(字距調整,測量,變換和緩衝)。
如今你有了足夠的知識可以以FreeType2爲基礎構建一個至關好的文字服務,並且要是你願意,你能夠在這裏止步了。