第一步 -- 簡易的字形裝載
介紹
這是「FreeType2 教程」的第一部分。它將教會你如何:
* 初始化庫
* 經過建立一個新的 face 對象來打開一個字體文件
* 以點或者象素的形式選擇一個字符大小
* 裝載一個字形(glyph)圖像,並把它轉換爲位圖
* 渲染一個簡單的字符串
* 容易地渲染一個旋轉的字符串
1.頭文件
下面的內容是編譯一個使用了FreeType2庫的應用程序所須要的指令。請謹慎閱讀,自最近一次版本更新後咱們已經更改了少量東西。
1.FreeType2 include 目錄
你必須把FreeType2頭文件的目錄添加到編譯包含(include)目錄中。
注意,如今在Unix系統,你能夠運行freetype-config腳本加上--cflags選項來得到正確的編譯標記。這個腳本也能夠用來檢查安裝在你係統中的庫的版本,以及須要的庫和鏈接標記。
2. 包含名爲ft2build.h的文件
Ft2build.h包含了接下來要#include的公共FreeType2頭文件的宏聲明。
3. 包含主要的FreeType2 API頭文件
你要使用FT_FREETYPE_H宏來完成這個工做,就像下面這樣:
#include <ft2build.h>
#include FT_FREETYPE_H
FT_FREETYPE_H是在ftheader.h中定義的一個特別的宏。Ftheader.h包含了一些安裝所特定的宏,這些宏指名了FreeType2 API的其餘公共頭文件。
你能夠閱讀「FreeType 2 API參考」的這個部分來得到頭文件的完整列表。
#include語句中宏的用法是服從ANSI的。這有幾個緣由:
* 這能夠避免一些使人痛苦的與FreeType 1.x公共頭文件的衝突。
* 宏名字不受限於DOS的8.3文件命名限制。象FT_MULTIPLE_MASTERS_H或FT_SFNT_NAMES_H這樣的名字比真實的文件名ftmm.h和fsnames.h更具可讀性而且更容易理解。
* 它容許特別的安裝技巧,咱們不在這裏討論它。
注意:從FreeType 2.1.6開始,舊式的頭文件包含模式將不會再被支持。這意味着如今若是你作了象下面那樣的事情,你將獲得一個錯誤:
#include <freetype/freetype.h>
#include <freetype/ftglyph.h>
. . .
2. 初始化庫
簡單地建立一個FT_Library類型的變量,例如library,而後象下面那樣調用函數FT_Init_FreeType:
#include <ft2build.h>
#include FT_FREETYPE_H
FT_LIBRARY library;
. . .
Error = FT_Init_FreeType ( &library );
If ( error )
{
. . . 當初始化庫時發生了一個錯誤 . . .
}
這個函數負責下面的事情:
* 它建立一個FreeType 2庫的新實例,而且設置句柄library爲它。
* 它裝載庫中FreeType所知道的每個模塊。除了別的之外,你新建的library對象能夠優雅地處理TrueType, Type 1, CID-keyed 和OpenType/CFF字體。
就像你所看到的,這個函數返回一個錯誤代碼,如同FreeType API的大部分其餘函數同樣。值爲0的錯誤代碼始終意味着操做成功了,不然,返回值指示錯誤,library設爲NULL。
3.裝載一個字體face
a.從一個字體文件裝載
應用程序經過調用FT_New_Face建立一個新的face對象。一個face對象描述了一個特定的字樣和風格。例如,’Times New Roman Regular’和’Times New Roman Italic’對應兩個不一樣的face。
FT_Library library;
FT_Face face;
error = FT_Init_FreeType( &library );
if ( error ) { ... }
error = FT_New_Face( library,
"/usr/share/fonts/truetype/arial.ttf",
0,
&face );
if ( error == FT_Err_Unknown_File_Format )
{
... 能夠打開和讀這個文件,但不支持它的字體格式
}
else if ( error )
{
... 其它的錯誤碼意味着這個字體文件不能打開和讀,或者簡單的說它損壞了...
}
就如你所想到的,FT_NEW_Face打開一個字體文件,而後設法從中提取一個face。它的參數爲:
Library
一個FreeType庫實例的句柄,face對象從中創建
Filepathname
字體文件路徑名(一個標準的C字符串)
Face_index
某些字體格式容許把幾個字體face嵌入到同一個文件中。
這個索引指示你想裝載的face。
若是這個值太大,函數將會返回一個錯誤。Index 0老是正確的。
Face
一個指向新建的face對象的指針。
當失敗時其值被置爲NULL。
要知道一個字體文件包含多少個face,只要簡單地裝載它的第一個face(把face_index設置爲0),face->num_faces的值就指示出了有多少個face嵌入在該字體文件中。
b.從內存裝載
若是你已經把字體文件裝載到內存,你能夠簡單地使用FT_New_Memory_Face爲它新建一個face對象,以下所示:
FT_Library library;
FT_Face face;
error = FT_Init_FreeType( &library );
if ( error ) { ... }
error = FT_New_Memory_Face( library,
buffer,
size,
0,
&face );
if ( error ) { ... }
如你所看到的,FT_New_Memory_Face簡單地用字體文件緩存的指針和它的大小(以字節計算)代替文件路徑。除此以外,它與FT_New_Face的語義一致。
c.從其餘來源裝載(壓縮文件,網絡,等)
使用文件路徑或者預裝載文件到內存是簡單的,但還不足夠。FreeType 2能夠支持經過你本身實現的I/O程序來裝載文件。
這是經過FT_Open_Face函數來完成的。FT_Open_Face能夠實現使用一個自定義的輸入流,選擇一個特定的驅動器來打開,乃至當建立該對象時傳遞外部參數給字體驅動器。咱們建議你查閱「FreeType 2參考手冊」,學習如何使用它。
4.訪問face內容
一個face對象包含該face的所有全局描述信息。一般的,這些數據能夠經過分別查詢句柄來直接訪問,例如face->num_glyphs。
FT_FaceRec結構描述包含了可用字段的完整列表。咱們在這裏詳細描述其中的某些:
Num_glyphs
這個值給出了該字體face中可用的字形(glyphs)數目。簡單來講,一個字形就是一個字符圖像。但它不必定符合一個字符代碼。
Flags
一個32位整數,包含一些用來描述face特性的位標記。例如,標記FT_FACE_FLAG_SCALABLE用來指示該face的字體格式是可伸縮而且該字形圖像能夠渲染到任何字符象素尺寸。要了解face標記的更多信息,請閱讀「FreeType 2 API 參考」。
Units_per_EM
這個字段只對可伸縮格式有效,在其餘格式它將會置爲0。它指示了EM所覆蓋的字體單位的個數。
Num_fixed_size
這個字段給出了當前face中嵌入的位圖的個數。簡單來講,一個strike就是某一特定字符象素尺寸下的一系列字形圖像。例如,一個字體face能夠包含象素尺寸爲十、12和14的strike。要注意的是即便是可伸縮的字體格式野能夠包含嵌入的位圖!
Fixed_sizes
一個指向FT_Bitmap_Size成員組成的數組的指針。每個FT_Bitmap_Size指示face中的每個strike的水平和垂直字符象素尺寸。
注意,一般來講,這不是位圖strike的單元尺寸。
5.設置當前象素尺寸
對於特定face中與字符大小相關的信息,FreeType 2使用size對象來構造。例如,當字符大小爲12點時,使用一個size對象以1/64象素爲單位保存某些規格(如ascender或者文字高度)的值。
當FT_New_Face或它的親戚被調用,它會自動在face中新建一個size對象,並返回。該size對象能夠經過face->size直接訪問。
注意:一個face對象能夠同時處理一個或多個size對象,但只有不多程序員須要用到這個功能,於是,咱們決定簡化該API,(例如,每一個face對象只擁有一個size對象)可是這個特性咱們仍然經過附加的函數提供。
當一個新的face對象創建時,對於可伸縮字體格式,size對象默認值爲字符大小水平和垂直均爲10象素。對於定長字體格式,這個大小是未定義的,這就是你必須在裝載一個字形前設置該值的緣由。
使用FT_Set_Char_Size完成該功能。這裏有一個例子,它在一個300x300dpi設備上把字符大小設置爲16pt。
error = FT_Set_Char_Size(
face,
0,
16*64,
300,
300 );
注意:
* 字符寬度和高度以1/64點爲單位表示。一個點是一個1/72英寸的物理距離。一般,這不等於一個象素。
* 設備的水平和垂直分辨率以每英寸點數(dpi)爲單位表示。顯示設備(如顯示器)的常規值爲72dpi或96dpi。這個分辨率是用來從字符點數計算字符象素大小的。
* 字符寬度爲0意味着「與字符高度相同」,字符高度爲0意味着「與字符寬度相同」。對於其餘狀況則意味着指定不同的字符寬度和高度。
* 水平或垂直分辨率爲0時表示使用默認值72dpi。
* 第一個參數是face對象的句柄,不是size對象的。
這個函數計算對應字符寬度、高度和設備分辨率的字符象素大小。然而,若是你想本身指定象素大小,你能夠簡單地調用FT_Set_Pixel_Sizes,就像這樣:
error = FT_Set_Pixel_Sizes(
face,
0,
16 );
這個例子把字符象素設置爲16x16象素。如前所說的,尺寸中的任一個爲0意味着「與另外一個尺寸值相等」。
注意這兩個函數都返回錯誤碼。一般,錯誤會發生在嘗試對定長字體格式(如FNT或PCF)設置不在face->fixed_size數組中的象素尺寸值。
6.裝載一個字形圖像
a.把一個字符碼轉換爲一個字形索引
一般,一個應用程序想經過字符碼來裝載它的字形圖像。字符碼是一個特定編碼中表明該字符的數值。例如,字符碼64表明了ASCII編碼中的’A’。
一個face對象包含一個或多個字符表(charmap),字符表是用來轉換字符碼到字形索引的。例如,不少TrueType字體包含兩個字符表,一個用來轉換Unicode字符碼到字形索引,另外一個用來轉換Apple Roman編碼到字形索引。這樣的字體既能夠用在Windows(使用Unicode)和Macintosh(使用Apple Roman)。同時要注意,一個特定的字符表可能沒有覆蓋完字體裏面的所有字形。
當新建一個face對象時,它默認選擇Unicode字符表。若是字體沒包含Unicode字符表,FreeType會嘗試在字形名的基礎上模擬一個。注意,若是字形名是不標準的那麼模擬的字符表有可能遺漏某些字形。對於某些字體,包括符號字體和舊的亞洲手寫字體,Unicode模擬是不可能的。
咱們將在稍後敘述如何尋找face中特定的字符表。如今咱們假設face包含至少一個Unicode字符表,而且在調用FT_New_Face時已經被選中。咱們使用FT_Get_Char_Index把一個Unicode字符碼轉換爲字形索引,以下所示:
glyph_index = FT_Get_Char_Index( face, charcode );
這個函數會在face裏被選中的字符表中查找與給出的字符碼對應的字形索引。若是沒有字符表被選中,這個函數簡單的返回字符碼。
注意,這個函數是FreeType中罕有的不返回錯誤碼的函數中的一個。然而,當一個特定的字符碼在face中沒有字形圖像,函數返回0。按照約定,它對應一個特殊的字形圖像――缺失字形,一般會顯示一個框或一個空格。
b.從face中裝載一個字形
一旦你得到了字形索引,你即可以裝載對應的字形圖像。在不一樣的字體中字形圖像存儲爲不一樣的格式。對於固定尺寸字體格式,如FNT或者PCF,每個圖像都是一個位圖。對於可伸縮字體格式,如TrueType或者Type1,使用名爲輪廓(outlines)的矢量形狀來描述每個字形。一些字體格式可能有更特殊的途徑來表示字形(如MetaFont――但這個格式不被支持)。幸運的,FreeType2有足夠的靈活性,能夠經過一個簡單的API支持任何類型的字形格式。
字形圖像存儲在一個特別的對象――字形槽(glyph slot)中。就如其名所暗示的,一個字形槽只是一個簡單的容器,它一次只能容納一個字形圖像,能夠是位圖,能夠是輪廓,或者其餘。每個face對象都有一個字形槽對象,能夠經過face->glyph來訪問。它的字段在FT_GlyphSlotRec結構的文檔中解釋了。
經過調用FT_Load_Glyph來裝載一個字形圖像到字形槽中,以下:
error = FT_Load_Glyph(
face,
glyph_index,
load_flags );
load_flags的值是位標誌集合,是用來指示某些特殊操做的。其默認值是FT_LOAD_DEFAULT即0。
這個函數會設法從face中裝載對應的字形圖像:
* 若是找到一個對應該字形和象素尺寸的位圖,那麼它將會被裝載到字形槽中。嵌入的位圖老是比原生的圖像格式優先裝載。由於咱們假定對一個字形,它有更高質量的版本。這能夠用FT_LOAD_NO_BITMAP標誌來改變。
* 不然,將裝載一個該字形的原生圖像,把它伸縮到當前的象素尺寸,而且對應如TrueType和Type1這些格式,也會完成hinted操做。
字段face->glyph->format描述了字形槽中存儲的字形圖像的格式。若是它的值不是FT_GLYPH_FORMAT_BITMAP,你能夠經過FT_Render_Glyph把它直接轉換爲一個位圖。以下:
error = FT_Render_Glyph( face->glyph,
render_mode );
render_mode參數是一個位標誌集合,用來指示如何渲染字形圖像。把它設爲FT_RENDER_MODE_NORMAL渲染出一個高質量的抗鋸齒(256級灰度)位圖。這是默認狀況,若是你想生成黑白位圖,可使用FT_RENDER_MODE_MONO標誌。
一旦你生成了一個字形圖像的位圖,你能夠經過glyph->bitmap(一個簡單的位圖描述符)直接訪問,同時用glyph->bitmap_left和glyph->bitmap_top來指定起始位置。
要注意,bitmap_left是從字形位圖當前筆位置到最左邊界的水平距離,而bitmap_top是從筆位置(位於基線)到最高邊界得垂直距離。他麼是正數,指示一個向上的距離。
下一部分將給出字形槽內容的更多細節,以及如何訪問特定的字形信息(包括度量)。
c.使用其餘字符表
如前面所說的,當一個新face對象建立時,它會尋找一個Unicode字符表而且選擇它。當前被選中的字符表能夠經過face->charmap訪問。當沒有字符表被選中時,該字段爲NULL。這種狀況在你從一個不含Unicode字符表的字體文件(這種文件如今很是罕見)建立一個新的FT_Face對象時發生。
有兩種途徑能夠在FreeType 2中選擇不一樣的字符表。最輕鬆的途徑是你所需的編碼已經有對應的枚舉定義在FT_FREETYPE_H中,例如FT_ENCODING_BIG5。在這種狀況下,你能夠簡單地調用FT_Select_CharMap,以下:
error = FT_Select_CharMap(
face,
FT_ENCODING_BIG5 );
另外一種途徑是手動爲face解析字符表。這經過face對象的字段num_charmaps和charmaps(注意這是複數)來訪問。如你想到的,前者是face中的字符表的數目,後者是一個嵌入在face中的指向字符表的指針表(a table of pointers to the charmaps)。
每個字符表有一些可見的字段,用來更精確地描述它,主要用到的字段是charmap->platform_id和charmap->encoding_id。這二者定義了一個值組合,以更普
通的形式用來描述該字符表。
每個值組合對應一個特定的編碼。例如組合(3,1)對應Unicode。組合列表定義在TrueType規範中,但你也可使用文件FT_TRUETYPE_IDS_H來處理它們,該文件定義了幾個有用的常數。
要選擇一個具體的編碼,你須要在規範中找到一個對應的值組合,而後在字符表列表中尋找它。別忘記,因爲歷史的緣由,某些編碼會對應幾個值組合。這裏是一些代碼:
FT_CharMap found = 0;
FT_CharMap charmap;
int n;
for ( n = 0; n < face->num_charmaps; n++ )
{
charmap = face->charmaps[n];
if ( charmap->platform_id == my_platform_id &&
charmap->encoding_id == my_encoding_id )
{
found = charmap;
break;
}
}
if ( !found ) { ... }
error = FT_Set_CharMap( face, found );
if ( error ) { ... }
一旦某個字符表被選中,不管經過FT_Select_CharMap仍是經過FT_Set_CharMap,它都會在後面的FT_Get_Char_Index調用使用。
d.字形變換
當字形圖像被裝載時,能夠對該字形圖像進行仿射變換。固然,這隻適用於可伸縮(矢量)字體格式。
簡單地調用FT_Set_Transform來完成這個工做,以下:
error = FT_Set_Transform(
face,
&matrix,
&delta );
這個函數將對指定的face對象設置變換。它的第二個參數是一個指向FT_Matrix結
構的指針。該結構描述了一個2x2仿射矩陣。第三個參數是一個指向FT_Vector結構的指針。該結構描述了一個簡單的二維矢量。該矢量用來在2x2變換後對字形圖像平移。
注意,矩陣指針能夠設置爲NULL,在這種狀況下將進行恆等變換。矩陣的係數是16.16形式的固定浮點單位。
矢量指針也能夠設置爲NULL,在這種狀況下將使用(0, 0)的delta。矢量座標以一個象素的1/64爲單位表示(即一般所說的26.6固定浮點格式)。
注意:變換將適用於使用FT_Load_Glyph裝載的所有字形,而且徹底獨立於任何hinting處理。這意味着你對一個12象素的字形進行2倍放大變換不會獲得與24象素字形相同的結果(除非你禁止hints)。
若是你須要使用非正交變換和最佳hints,你首先必須把你的變換分解爲一個伸縮部分和一個旋轉/剪切部分。使用伸縮部分來計算一個新的字符象素大小,而後使用旋轉/剪切部分來調用FT_Set_Transform。這在本教程的後面部分有詳細解釋。
同時要注意,對一個字形位圖進行非同一性變換將產生錯誤。
7. 簡單的文字渲染
如今咱們將給出一個很是簡單的例子程序,該例子程序渲染一個8位Latin-1文本字符串,而且假定face包含一個Unicode字符表。
該程序的思想是創建一個循環,在該循環的每一次迭代中裝載一個字形圖像,把它轉換爲一個抗鋸齒位圖,把它繪製到目標表面(surface)上,而後增長當前筆的位置。
a.基本代碼
下面的代碼完成咱們上面提到的簡單文本渲染和其餘功能。
FT_GlyphSlot slot = face->glyph;
int pen_x, pen_y, n;
... initialize library ...
... create face object ...
... set character size ...
pen_x = 300;
pen_y = 200;
for ( n = 0; n < num_chars; n++ )
{
FT_UInt glyph_index;
glyph_index = FT_Get_Char_Index( face, text[n] );
error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );
if ( error )
continue;
error = FT_Render_Glyph( face->glyph, ft_render_mode_normal );
if ( error )
continue;
my_draw_bitmap( &slot->bitmap,
pen_x + slot->bitmap_left,
pen_y - slot->bitmap_top );
pen_x += slot->advance.x >> 6;
pen_y += slot->advance.y >> 6;
}
這個代碼須要一些解釋:
* 咱們定義了一個名爲slot的句柄,它指向face對象的字形槽。(FT_GlyphSlot類型是一個指針)。這是爲了便於避免每次都使用face->glyph->XXX。
* 咱們以slot->advance增長筆位置,slot->advance符合字形的步進寬度(也就是一般所說的走格(escapement))。步進矢量以象素的1/64爲單位表示,而且在每一次迭代中刪減爲整數象素。
* 函數my_draw_bitmap不是FreeType的一部分,但必須由應用程序提供以用來繪製位圖到目標表面。在這個例子中,該函數以一個FT_Bitmap描述符的指針和它的左上角位置爲參數。
* Slot->bitmap_top的值是正數,指字形圖像頂點與pen_y的垂直距離。咱們假定my_draw_bitmap採用的座標使用同樣的約定(增長Y值對應向下的掃描線)。咱們用pen_y減它,而不是加它。
b.精練的代碼
下面的代碼是上面例子程序的精練版本。它使用了FreeType 2中咱們尚未介紹的特性和函數,咱們將在下面解釋:
FT_GlyphSlot slot = face->glyph;
FT_UInt glyph_index;
int pen_x, pen_y, n;
... initialize library ...
... create face object ...
... set character size ...
pen_x = 300;
pen_y = 200;
for ( n = 0; n < num_chars; n++ )
{
error = FT_Load_Char( face, text[n], 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;
}
咱們簡化了代碼的長度,但它完成相同的工做:
* 咱們使用函數FT_Loac_Char代替FT_Load_Glyph。如你大概想到的,它至關於先調用GT_Get_Char_Index而後調用FT_Get_Load_Glyph。
* 咱們不使用FT_LOAD_DEFAULT做爲裝載模式,使用FT_LOAD_RENDER。它指示了字形圖像必須當即轉換爲一個抗鋸齒位圖。這是一個捷徑,能夠取消明顯的調用FT_Render_Glyph,但功能是相同的。
注意,你也能夠指定經過附加FT_LOAD_MONOCHROME裝載標誌來得到一個單色位圖。
c.更高級的渲染
如今,讓咱們來嘗試渲染變換文字(例如經過一個環)。咱們能夠用FT_Set_Transform來完成。這裏是示例代碼:
FT_GlyphSlot slot;
FT_Matrix matrix;
FT_UInt glyph_index;
FT_Vector pen;
int n;
... initialize library ...
... create face object ...
... set character size ...
slot = face->glyph;
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 );
pen.x = 300 * 64;
pen.y = ( my_target_height - 200 ) * 64;
for ( n = 0; n < num_chars; n++ )
{
FT_Set_Transform( face, &matrix, &pen );
error = FT_Load_Char( face, text[n], FT_LOAD_RENDER );
if ( error )
continue;
my_draw_bitmap( &slot->bitmap,
slot->bitmap_left,
my_target_height - slot->bitmap_top );
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
一些說明:
* 如今咱們使用一個FT_Vector類型的矢量來存儲筆位置,其座標以象素的1/64爲單位表示,而且倍增。該位置表示在笛卡兒空間。
* 不一樣於系統典型的對位圖使用的座標系(其最高的掃描線是座標0),FreeType中,字形圖像的裝載、變換和描述老是採用笛卡兒座標系(這意味着增長Y對應向上的掃描線)。所以當咱們定義筆位置和計算位圖左上角時必須在兩個系統之間轉換。
* 咱們對每個字形設置變換來指示旋轉矩陣以及使用一個delta來移動轉換後的圖像到當前筆位置(在笛卡兒空間,不是位圖空間)。結果,bitmap_left和bitmap_top的值對應目標空間象素中的位圖原點。所以,咱們在調用my_draw_bitmap時不在它們的值上加pen.x或pen.y。
* 步進寬度總會在變換後返回,這就是它能夠直接加到當前筆位置的緣由。注意,此次它不會四捨五入。
一個例子完整的源代碼能夠在這裏找到。
要很注意,雖然這個例子比前面的更復雜,可是變換效果是徹底一致的。所以它能夠做爲一個替換(但更強大)。
然而該例子有少量缺點,咱們將在本教程的下一部分中解釋和解決。
結論
在這個部分,你已經學習了FreeType2的基礎以及渲染旋轉文字的充分知識。程序員