FreeType字體程序庫介紹

FreeType字體程序庫介紹

『FreeType簡介』



FreeType庫是一個完全免費(開 源)的、高質量的且可移植的字體引擎,它提供統一的接口來訪問多種字體格式文件,包括TrueType, OpenType, Type1, CID, CFF, Windows FON/FNT, X11 PCF等。支持單色位圖、反走樣位圖的渲染。FreeType庫是高度模塊化的程序庫,雖然它是使用ANSI C開發,但是採用面向對象的思想(下文中將會介紹到)。因此,FreeType的用戶可以靈活地對它進行裁剪,例如我們在使用過程中,僅僅使用 TrueType字體格式的處理,就可以將其他和這個格式無關的代碼通過若干宏定義的取消就可以達到裁剪目的,這可以保證最後的二進制代碼的緊湊性。例 如,我們對TrueType格式處理的裁剪,使用單色位圖渲染,最後的二進制代碼大約只有25KB。

這個庫可以用於各種圖形處理系統,當然如果你想自己控制字體的渲染、佈局的話。另外,拿它來當作學習的範例也是非常不錯,其中包含一些優秀的設計思想和比較成熟的算法。

本 文大多是對FreeType文檔的翻譯,有些地方如果翻譯的不好,還請原諒,可以訪問[url]http://www.infomall.cn/cgi- bin/mallgate/20030816/http://www.freetype.org/[/url]以獲得最新的信息。這裏先介紹一下 FreeType的設計思路和字形規範,也希望和國內使用FreeType庫的朋友多多交流。



『FreeType設計』

一、組件和API
FT可以看作是一組組件,每個組件負責一部分任務,它們包括

Ÿ 客戶應用程序一般會調用FT高層API,它的功能都在一個組件中,叫做基礎層。

Ÿ 根據上下文和環境,基礎層會調用一個或多個模塊進行工作,大多數情況下,客戶應用程序不知道使用那個模塊。

Ÿ 基礎層還包含一組例程來進行一些共通處理,例如內存分配,列表處理、io流解析、固定點計算等等,這些函數可以被模塊隨意調用,它們形成了一個底層基礎API。

如下圖,表明它們的關係


另外,

Ÿ 爲了一些特殊的構建,基礎層的有些部分可以替換掉,也可以看作組件。例如ftsystem組件,負責實現內存管理和輸入流訪問,還有ftinit,負責庫初始化。

Ÿ FT還有一些可選的組件,可以根據客戶端應用靈活使用,例如ftglyph組件提供一個簡單的API來管理字形映象,而不依賴它們內部表示法。或者是訪問特定格式的特性,例如ftmm組件用來訪問和管理Type1字體中Multiple Masters數據。

Ÿ 最後,一個模塊可以調用其他模塊提供的函數,這對在多個字體驅動模塊中共享代碼和表非常有用,例如truetype和cff模塊都使用sfnt模塊提供的函數。

見下圖,是對上圖的一個細化。


請注意一些要點:

Ÿ 一個可選的組件可以用在高層API,也可以用在底層API,例如上面的ftglyph;

Ÿ 有些可選組件使用模塊特定的接口,而不是基礎層的接口,上例中,ftmm直接訪問Type1模塊來訪問數據;

Ÿ 一個可替代的組件能夠提供一個高層API的函數,例如,ftinit提供FT_Init_FreeType()

二、公共對象和類
1、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、 類繼承通過將基類包裝到一個新類中實現,例如,我們定義一個foobar類,從foo類繼承,可以實現爲

typedef struct FT_FooBarRec_ * FT_FooBar;

typedef struct FT_FooBarRec_

{

FT_FooRec root; //基類



}FT_FooBarRec;

可以看到,將一個FT_FooRec放在FT_FooBarRec定義的開始,並約定名爲root,可以確保一個foobar對象也是一個foo對象。

在實際使用中,可以進行類型轉換。

後面

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直接訪問。



6、FT_CharMap類
FT_CharMap類型用來作爲一個字符地圖對象的句柄,一個字符圖是一種表或字典,用來將字符碼從某種編碼轉換成字體的字形索引。

單個外觀可能包含若干字符圖,每個對應一個指定的字符指令系統,例如Unicode、Apple Roman、Windows codepages等等。

每個FT_CharMap對象包含一個platform和encoding屬性,用來標識它對應的字符指令系統。每個字體格式都提供它們自己的FT_CharMapRec的繼承類型並實現它們。

7 對象關係

三、內部對象和類
1、內存管理
所 有內存管理操作通過基礎層中3個特定例程完成,叫做FT_Alloc、FT_Realloc、FT_Free,每個函數需要一個FT_Memory句柄作 爲它的第一個參數。它是一個用來描述當前內存池/管理器對象的指針。在庫初始化時,在FT_Init_FreeType中調用函數 FT_New_Memory創建一個內存管理器,這個函數位於ftsystem部件當中。

缺省情況下,這個管理器使用ANSI malloc、realloc和free函數,不過ftsystem是基礎層中一個可替換的部分,庫的特定構建可以提供不同的內存管理器。即使使用缺省的 版本,客戶程序仍然可以提供自己的內存管理器,通過如下的步驟,調用FT_Init_FreeType實現:

1. 手工創建一個FT_Memory對象,FT_MemoryRec位於公共文件<freetype/ftsystem.h>中。

2. 使用你自己的內存管理器,調用FT_New_Library()創建一個新的庫實例。新的庫沒有包含任何已註冊的模塊。

3. 通過調用函數FT_Add_Default_Modules()(在ftinit部件中)註冊缺省的模塊,或者通過反覆調用FT_Add_Module手工註冊你的驅動。

2、輸入流
字 體文件總是通過FT_Stream對象讀取,FT_StreamRec的定義位於公共文 件<freetype/ftsystem.h>中,可以允許客戶開發者提供自己的流實現。FT_New_Face()函數會 自動根據他第二個參數,一個C路徑名創建一個新的流對象。它通過調用由ftsystem部件提供的FT_New_Stream()完成,後者時可替換的, 在不同平臺上,流的實現可能大不一樣。

舉例來說,流的缺省實現位於文件src/base/ftsystem.c並使用ANSI fopen/fseek和fread函數。不過在FT2的Unix版本中,提供了另一個使用內存映射文件的實現,對於主機系統來說,可以大大提高速度。

FT區分基於內存和基於磁盤的流,對於前者,所有數據在內存直接訪問(例如ROM、只寫靜態數據和內存映射文件),而後者,使用幀(frame)的概念從字體文件中讀出一部分,使用典型的seek/read操作並暫時緩衝。

FT_New_Memory_Face 函數可以用來直接從內存中可讀取的數據創建/打開一個FT_Face對象。最後,如果需要客戶輸入流,客戶程序能夠使用FT_Open_Face()函數 接受客戶輸入流。這在壓縮或遠程字體文件的情況下,以及從特定文檔抽取嵌入式字體文件非常有用。

注意每個外觀擁有一個流,並且通過FT_Done_Face被銷燬。總的來說,保持多個FT_Face在打開狀態不是一個很好的主意。

3、模塊
FT2 模塊本身是一段代碼,庫調用FT_Add_Moudle()函數註冊模塊,併爲每個模塊創建了一個FT_Module對象。FT_ModuleRec的定 義對客戶程序不是公開的,但是每個模塊類型通過一個簡單的公共結構FT_Module_Class描述,它定義 在<freetype/ftmodule.h>中,後面將詳述。

當調用FT_Add_Module是,需要指定一個FT_Module_Class結構的指針,它的聲明如下:

FT_Error FT_Add_Module( FT_Library library,

const FT_Module_Class* clazz);

調用這個函數將作如下操作:

Ÿ 檢查庫中是否已經有對應FT_Module_Class指名的模塊對象;

Ÿ 如果是,比較模塊的版本號看是否可以升級,如果模塊類的版本號小於已裝入的模塊,函數立即返回。當然,還要檢查正在運行的FT版本是否滿足待裝模塊所需FT的版本。

Ÿ 創建一個新的FT_Module對象,使用模塊類的數據的標誌決定它的大小和如何初始化;

Ÿ 如果在模塊類中有一個模塊初始器,它將被調用完成模塊對象的初始化;

Ÿ 新的模塊加入到庫的“已註冊”模塊列表中,對升級的情況,先前的模塊對象只要簡單的銷燬。

注意這個函數並不返回FT_Module句柄,它完全是庫內部的事情,客戶程序不應該擺弄他。最後,要知道FT2識別和管理若干種模塊,這在後面將有詳述,這裏列舉如下:

Ÿ 渲染器模塊用來將原始字形映象轉換成位圖或象素圖。FT2自帶兩個渲染器,一個是生成單色位圖,另一個生成高質量反走樣的象素圖。

Ÿ 字體驅動模塊用來支持多種字體格式,典型的字體驅動需要提供特定的FT_Face、FT_Size、FT_GlyphSlot和FT_CharMap的實現;

Ÿ 助手模塊被多個字體驅動共享,例如sfnt模塊分析和管理基於SFNT字體格式的表,用於TrueType和OpenType字體驅動;

Ÿ 最後,auto-hinter模塊在庫設計中有特殊位置,它不管原始字體格式,處理向量字形輪廓,使之產生優質效果。

注意每個FT_Face對象依據原始字體文件格式,都屬於相應的字體驅動。這就是說,當一個模塊從一個庫實例移除/取消註冊後,所有的外觀對象都被銷燬(通常是調用FT_Remove_Module()函數)。

因此,你要注意當你升級或移除一個模塊時,沒有打開FT_Face對象,因爲這會導致不預期的對象刪除。

4、庫
現在來說說FT_Library對象,如上所述,一個庫實例至少包含如下:

Ÿ 一個內存管理對象(FT_Memory),用來在實例中分配、釋放內存;

Ÿ 一個FT_Module對象列表,對應“已安裝”或“已註冊”的模塊,它可以隨時通過FT_Add_Module()和FT_Remove_Module()管理;

Ÿ 記住外觀對象屬於字體驅動,字體驅動模塊屬於庫。

還有一個對象屬於庫實例,但仍未提到:raster pool

光柵池是一個固定大小的一塊內存,爲一些內存需要大的操作作爲內部的“草稿區域”,避免內存分配。例如,它用在每個渲染器轉換一個向量字形輪廓到一個位圖時(這其實就是它爲何叫光柵池的原因)。

光柵池的大小在初始化的時候定下來的(缺省爲16k字節),運行期不能修改。當一個瞬時操作需要的內存超出這個池的大小,可以另外分配一塊作爲例外條件,或者是遞歸細分任務,以確保不會超出池的極限。

這種極度的內存保守行爲是爲了FT的性能考慮,特別在某些地方,如字形渲染、掃描線轉換等。

5、總結
最後,下圖展示的上面所述內容,他表示FT基本設計的對象圖




四、模塊類
在FT中有若干種模塊

Ÿ 渲染模塊,用來管理可縮放的字形映象。這意味這轉換它們、計算邊框、並將它們轉換成單色和反走樣位圖。FT可以處理任何類型的字形映像,只要爲它提供了一個渲染模塊,FT缺省帶了兩個渲染器

raster
支持從向量輪廓(由FT_Outline對象描述)到單色位圖的轉換

smooth
支持同樣的輪廓轉換到高質量反走樣的象素圖,使用256級灰度。這個渲染器也支持直接生成span。


Ÿ 字體驅動模塊,用來支持一種或多種特定字體格式,缺省情況下,FT由下列字體驅動

truetype
支持TrueType字體文件

type1
支持PostScript Type1字體,可以是二進制(.pfb)和ASCII(.pfa)格式,包括Multiple Master字體

cid
支持Postscript CID-keyed字體

cff
支持OpenType、CFF和CEF字體(CEF是CFF的一個變種,在Adobe的SVG Viewer中使用

winfonts
支持Windows位圖字體,.fon和.fnt


字體驅動可以支持位圖和可縮放的字形映象,一個特定支持Bezier輪廓的字體驅動通過FT_Outline可以提供他自己的hinter,或依賴FT的autohinter模塊。

Ÿ 助手模塊,用來處理一些共享代碼,通常被多個字體驅動,甚至是其他模塊使用,缺省的助手如下

sfnt
用來支持基於SFNT存儲綱要的字體格式,TrueType和OpenType字體和其他變種

psnames
用來提供有關字形名稱排序和Postscript編碼/字符集的函數。例如他可以從一個Type1字形名稱字典中自動合成一個Unicode字符圖。

psaux
用來提供有關Type1字符解碼的函數,type1、cid和cff都需要這個特性


最後,autohinter模塊在FT中是特殊角色,當一個字體驅動沒有提供自己的hint引擎時,他可以在字形裝載時處理各自的字形輪廓。



1 FT_Module_Class結構
2 FT_Module類型
『FT字形規範』

一、基本印刷概念
1、字體文件、格式和信息
字 體是一組可以被顯示和打印的多樣的字符映像,在單個字體中共享一些共有的特性,包括外表、風格、襯線等。按印刷領域的說法,它必須區別一個字體家族和多種 字體外觀,後者通常是從同樣的模板而來,但是風格不同。例如,Palatino Regular 和 Palatino Italic是兩種不同的外觀,但是屬於同樣的家族Palatino。

單個字體術語根據上下文既可以指家族也可指外觀。例如,大多文 字處理器的用戶用字體指不同的字體家族,然而,大多這些家族根據它們的格式會通過多個數據文件實現。對於TrueType來講,通常是每個外觀一個文件 (arial.ttf對應Arial Regular外觀,ariali.ttf對應Arial Italic外觀)這個文件也叫字體,但是實際上只是一個字體外觀。

數字字體是一個可以包含一個和多個字體外觀的數據文件,它們每個 都包含字符映像、字符度量,以及其他各種有關文本佈局和特定字符編碼的重要信息。對有些難用的格式,像Adobe的Type1,一個字體外觀由幾個文件描 述(一個包含字符映象,一個包含字符度量等)。在這裏我們忽略這種情況,只考慮一個外觀一個文件的情況,不過在FT2.0中,能夠處理多文件字體。

爲了方便說明,一個包含多個外觀的字體文件我們叫做字體集合,這種情況不多見,但是多數亞洲字體都是如此,它們會包含兩種或多種表現形式的映像,例如橫向和縱向佈局。



2、字符映象和圖
字 符映象叫做字形,根據書寫、用法和上下文,單個字符能夠有多個不同的映象,即多個字形。多個字符也可以有一個字形(例如Roman??)。字符和字形之間 的關係可能是非常複雜,本文不多述。而且,多數字體格式都使用不太難用的方案存儲和訪問字形。爲了清晰的原因,當說明FT時,保持下面的觀念

Ÿ 一個字體文件包含一組字形,每個字形可以存成位圖、向量表示或其他結構(更可縮放的格式使用一種數學表示和控制數據/程序的結合方式)。這些字形可以以任意順序存在字體文件中,通常通過一個簡單的字形索引訪問。

Ÿ 字體文件包含一個或多個表,叫做字符圖,用來爲某種字符編碼將字符碼轉換成字形索引,例如ASCII、Unicode、Big5等等。單個字體文件可能包 含多個字符圖,例如大多TrueType字體文件都會包含一個Apple特定的字符圖和Unicode字符圖,使它在Mac和Windows平臺都可以使 用。

3、字符和字體度量
每個字符映象都關聯多種度量,被用來在渲染文本時,描述如何放置和管理它們。在後面會有詳述,它們和字形位置、光標步進和文本佈局有關。它們在渲染一個文本串時計算文本流時非常重要。

每個可縮放的字體格式也包含一些全局的度量,用概念單位表示,描述同一種外觀的所有字形的一些特性,例如最大字形外框,字體的上行字符、下行字符和文本高度等。

雖然這些度量也會存在於一些不可縮放格式,但它們只應用於一組指定字符維度和分辨率,並且通常用象素表示。



二、字形輪廓
1、象素、點和設備解析度
當處理計算機圖形程序時,指定象素的物理尺寸不是正方的。通常,輸出設備是屏幕或打印機,在水平和垂直方向都有多種分辨率,當渲染文本是要注意這些情況。

定 義設備的分辨率通常使用用dpi(每英寸點(dot)數)表示的兩個數,例如,一個打印機的分辨率爲300x600dpi表示在水平方向,每英寸有300 個象素,在垂直方向有600個象素。一個典型的計算機顯示器根據它的大小,分辨率不同(15’’和17’’顯示器對640x480象素大小不同),當然圖 形模式分辨率也不一樣。

所以,文本的大小通常用點(point)表示,而不是用設備特定的象素。點是一種簡單的物理單位,在數字印刷中,一點等於1/72英寸。例如,大多羅馬書籍使用10到14點大小印刷文字內容。

可以用點數大小來計算象素數,公式如下:

象素數 = 點數*分辨率/72

分辨率用dpi表示,因爲水平和垂直分辨率可以不同,單個點數通常定義不同象素文本寬度和高度。

2、向量表示
字體輪廓的源格式是一組封閉的路徑,叫做輪廓線。每個輪廓線劃定字形的外部或內部區域,它們可以是線段或是Bezier曲線。

曲 線通過控制點定義,根據字體格式,可以是二次(conic Beziers)或三次(cubic Beziers)多項式。在文獻中,conic Bezier通常稱爲quadratic Beziers。因此,輪廓中每個點都有一個標誌表示它的類型是一般還是控制點,縮放這些點將縮放整個輪廓。

每個字形最初的輪廓點放置在一個不可分割單元的網格中,點通常在字體文件中以16位整型網格座標存儲,網格的原點在(0,0),它的範圍是-16384到-16383(雖然有的格式如Type1使用浮點型,但爲簡便起見,我們約定用整型分析)。

網格的方向和傳統數學二維平面一致,x軸從左到右,y軸從下到上。

在創建字形輪廓時,一個字體設計者使用一個假想的正方形,叫做EM正方形。他可以想象成一個畫字符的平面。正方形的大小,即它邊長的網格單元是很重要的,原因是

Ÿ 它是用來將輪廓縮放到指定文本尺寸的參考,例如在300x300dpi中的12pt大小對應12*300/72=50象素。從網格單元縮放到象素可以使用下面的公式

象素數 = 點數 × 分辨率/72

象素座標= 網格座標*象素數/EM大小

Ÿ EM尺寸越大,可以達到更大的分辨率,例如一個極端的例子,一個4單元的EM,只有25個點位置,顯然不夠,通常TrueType字體之用2048單元的EM;Type1 PostScript字體有一個固定1000網格單元的EM,但是點座標可以用浮點值表示。

注意,字形可以自由超出EM正方形。網格單元通常交錯字體單元或EM單元。上邊的象素數並不是指實際字符的大小,而是EM正方形顯示的大小,所以不同字體,雖然同樣大小,但是它們的高度可能不同。

3、Hinting和位圖渲染
存儲在一個字體文件中的輪廓叫“主”輪廓,它的點座標用字體單元表示,在它被轉換成一個位圖時,它必須縮放至指定大小。這通過一個簡單的轉換完成,但是總會產生一些不想要的副作用,例如像字母E和H,它們主幹的寬度和高度會不相同。

所 以,優秀的字形渲染過程在縮放“點”是,需要通過一個網格對齊(grid-fitting)的操作(通常叫hinting),將它們對齊到目標設備的象素 網格。這主要目的之一是爲了確保整個字體中,重要的寬度和高度能夠一致。例如對於字符I和T來說,它們那個垂直筆劃要保持同樣象素寬度。另外,它的目的還 有管理如stem和overshoot的特性,這在小象素字體會引起一些問題。

有若干種方式來處理網格對齊,多數可縮放格式中,每種字形輪廓都有一些控制數據和程序。

Ÿ 顯式網格對齊

TrueType格式定義了一個基於棧的虛擬機(VM),可以藉助多於200中操作碼(大多是幾何操作)來編寫程序,每個字形都由一個輪廓和一個控制程序組成,後者可以處理實際的網格對齊,他由字體設計者定義。

Ÿ 隱式網格對齊(也叫hinting)

Type1格式有一個更簡單的方式,每個字形由一個輪廓以及若干叫hints的片斷組成,後者用來描述字形的某些重要特性,例如主幹的存在、某些寬度勻稱性等諸如此類。沒有多少種hint,要看渲染器如何解釋hint來產生一個對齊的輪廓。

Ÿ 自動網格對齊

有些格式很簡單,沒有包括控制信息,將字體度量如步進、寬度和高度分開。要靠渲染器來猜測輪廓的一些特性來實現得體的網格對齊。

下面總結了每種方案的優點和缺點

方案
優點
缺點

顯式
質量:對小字體有很好的結果,這對屏幕顯示非常重要。

一致性:所有渲染器產生同樣的字形位圖。
速度:如果程序很複雜,解釋字節碼很慢

大小:字形程序會很長。

技術難度:編寫優秀的hinting程序非常難,沒有好的工具支持。

隱式
大小:Hint通常比顯式字形程序小的多

速度:網格對齊會非常快
質量:小字體不好,最後結合反走樣

不一致。不同渲染器結果不同,甚至同一引擎不同版本也不同。

自動
大小:不需要控制信息,導致更小的字體文件

速度:依賴對齊算法,通常比顯式對齊快。
質量:小字體不好,最後結合反走樣

速度:依賴算法

不一致:不同渲染器結果不同,甚至同一引擎不同版本也不同。




三、字形度量
1、基線(baseline)、筆(pen)和佈局(layout)
基線是一個假想的線,用來在渲染文本時知道字形,它可以是水平(如Roman)和是垂直的(如中文)。而且,爲了渲染文本,在基線上有一個虛擬的點,叫做筆位置(pen position)或原點(origin),他用來定位字形。

每種佈局使用不同的規約來放置字形:

Ÿ 對水平佈局,字形簡單地擱在基線上,通過增加筆位置來渲染文本,既可以向右也可以向左增加。

兩個相鄰筆位置之間的距離是根據字形不同的,叫做步進寬度(advance width)。注意這個值總是正數,即使是從右往左的方向排列字符,如Arabic。這和文本渲染的方式有些不同。

筆位置總是放置在基線上。


Ÿ 對垂直佈局,字形在基線上居中放置:




2、印刷度量和邊界框
在指定字體中,定義了多種外觀度量。

Ÿ 上行高度(ascent)。從基線到放置輪廓點最高/上的網格座標,因爲Y軸方向是向上的,所以它是一個正值。

Ÿ 下行高度(descent)。從基線到放置輪廓點最低/下的網格座標,因爲Y軸方向是向上的,所以它是一個負值。

Ÿ 行距(linegap)。兩行文本間必須的距離,基線到基線的距離應該計算成

上行高度 - 下行高度 + 行距

Ÿ 邊界框(bounding box,bbox)。這是一個假想的框子,他儘可能緊密的裝入字形。通過四個值來表示,叫做xMin、yMin、xMax、yMax,對任何輪廓都可以計 算,它們可以是字體單元(測量原始輪廓)或者整型象素單元(測量已縮放的輪廓)。注意,如果不是爲了網格對齊,你無需知道這個框子的這個值,只需知道它的 大小即可。但爲了正確渲染一個對齊的字形,需要保存每個字形在基線上轉換、放置的重要對齊。

Ÿ 內部leading。這個概念從傳統印刷業而來,他表示字形出了EM正方形空間數量,通常計算如下

internal leading = ascent – descent – EM_size

Ÿ 外部leading。行距的別名。

3、跨距(bearing)和步進
每個字形都有叫跨距和步進的距離,它們的定義是常量,但是它們的值依賴佈局,同樣的字形可以用來渲染橫向或縱向文字。

Ÿ 左跨距或bearingX。從當前筆位置到字形左bbox邊界的水平距離,對水平佈局是正數,對垂直佈局大多是負值。

Ÿ 上跨距或bearingY。從基線到bbox上邊界的垂直距離,對水平佈局是正值,對垂直佈局是負值。

Ÿ 步進寬度或advanceX。當處理文本渲染一個字形後,筆位置必須增加(從左向右)或減少(從右向左)的水平距離。對水平佈局總是正值,垂直佈局爲null。

Ÿ 步進高度或advanceY。當每個字形渲染後,筆位置必須減少的垂直距離。對水平佈局爲null,對垂直佈局總是正值。

Ÿ 字形寬度。字形的水平長度。對未縮放的字體座標,它是bbox.xMax-bbox.xMin,對已縮放字形,它的計算要看特定情況,視乎不同的網格對齊而定。

Ÿ 字形高度。字形的垂直長度。對未縮放的字體座標,它是bbox.yMax-bbox.yMin,對已縮放字形,它的計算要看特定情況,視乎不同的網格對齊而定。

Ÿ 右跨距。只用於水平佈局,描述從bbox右邊到步進寬度的距離,通常是一個非負值。

advance_width – left_side_bearing – (xMax-xMin)



下圖是水平佈局所有的度量


下圖是垂直佈局的度量


4、網格對齊的效果
因 爲hinting將字形的控制點對齊到象素網格,這個過程將稍稍修改字符映象的尺寸,和簡單的縮放有所區別。例如,小寫字母m的映象在主網格中有時是一個 正方形,但是爲了使它在小象素大小情況下可以辨別,hinting試圖擴大它已縮放輪廓,以讓它三條腿區分開來,這將導致一個更大的字符位圖。

字形度量也會受網格對齊過程的影響:

Ÿ 映象的寬度和高度改變了,即使只是一個象素,對於小象素大小字形區別都很大;

Ÿ 映象的邊界框改變了,也改變了跨距;

Ÿ 步進必須更改,例如如果被hint的位圖比縮放的位圖大時,必須增加步進寬度,來反映擴大的字形寬度。

這有一些含義如下,

Ÿ 因爲hinting,簡單縮放字體上行或下行高度可能不會有正確的結果,一個可能的方法時保持被縮放上行高度的頂和被縮放下行高度的底。

Ÿ 沒有容易的方法去hint一個範圍內字形並步進它們寬度,因爲hinting對每個輪廓工作都不一樣。唯一的方法時單獨hint每個字形,並記錄返回值。有些格式,如TrueType,包含一些表對一些通用字符預先計算出它們的象素大小。

Ÿ hinting依賴最終字符寬度和高度的象素值,意味着它非常依賴分辨率,這個特性使得正確的所見即所得佈局非常難以實現。

在 FT中,對字形輪廓處理2D變換很簡單,但是對一個已hint的輪廓,需要注意專有地使用整型象素距離(意味着 FT_Outline_Translate()函數的參數應該都乘以64,因爲點座標都是26.6固定浮點格式),否則,變換將破壞hinter的工作, 導致非常難看的位圖。

5、文本寬度和邊界框
如上所示,指定字形的原點對應基線上筆的位置,沒有必要定位字形邊界框的某個角,這不像多數典型的位圖字體格式。有些情況,原點可以在邊界框的外邊,有時,也可以在裏邊,這要看給定的字形外形了。

同樣,字形的步進寬度是在佈局時應用於筆位置的增量,而不是字形的寬度,那是字形邊界的寬度。對文本串,具有相同的規約,這意味着:

指定文本串的邊界框沒有必要包含文本光標,也不需要後邊的字形放置在它的角上。

字符串的步進寬度和它的邊界框大小無關,特別時它在開始和最後包含空格或tab。

最後,附加的處理如間距調整能夠創建文本串,它的大小不直接依賴單獨字形度量並列排列。例如,VA的步進寬度不是V和A各自的步進之和。

四、字距調整
字距調整這個術語指用來在一個文本串中調整重合字形的相對位置的特定信息。

1、字距調整對
字距調整包括根據相鄰字形的輪廓修改它們之間的距離。例如T和y可以貼得更近一點,因爲y的上緣正好在T的右上角一橫的下邊。

當僅僅根據字形的標準寬度來佈局文本,一些連續的字符看上去有點太擠和太鬆,例如下圖中A和V的就顯得距離太遠。


比較一下下圖,同樣的單詞,A和V的距離拉近些


可以看到,這個調整可以導致很大的區別。有的字體外觀包含一個表,它包含文本佈局所需的指定字形對的字距距離。

Ÿ 這個對是順序的,AV對的距離和VA對不一定一致;

Ÿ 依據佈局或書寫,字距可以表示水平或垂直方向。

Ÿ 字距表示成網格單元,它們通常是X軸方向的,意味着負值表示兩個字形需要在水平方向放的更近一點。

2、應用字距調整
在渲染文本時應用字據調整是一個比較簡單的過程,只需要在寫下一個字形時,將縮放的字距加到筆位置即可。然而,正確的渲染器要考慮的更細一點。

“滑動點”問題是一個很好的例子:很多字體外觀包括一個大寫字符(如T、F)和一個點.之間的字距調整,以將點正好放置在前者的主腿的右側。


根據字符的外形,有時候需要在點和隨後的字符間作附加的調整,當應用“標準”的字距調整,上面的句子如下


這顯然太緊湊了。一個方案是,只在需要時滑動點,當然這需要對文本的意思有了解。如果當我們在渲染特定段落的最後一個點時,上面的調整就不適合了。這只是一個例子,還有很多其他例子顯示一個真正的印刷工人需要恰當地佈局文本。

有一個很簡單地算法,可以避免滑動點問題。

1. 在基線上放置第一個字形;

2. 將筆位置保存到pen1;

3. 根據第一個和第二個字形的字距距離調整筆位置;

4. 放置第二個字形,並計算下個筆位置,放到pen2;

5. 如果pen1大於pen2,使用pen1作爲下個筆位置,否則使用pen2。

五、文本處理
1、書寫簡單文本串
在第一個例子中,我們將生成一個簡單的Roman文字串,即採用水平的自左向右佈局,使用專有的象素度量,這個過程如下:

1. 將字符串轉換成一系列字形索引;

2. 將筆放置在光標位置;

3. 獲得或裝入字形映象;

4. 平移字形以使它的原點匹配筆位置;

5. 將字形渲染到目標設備;

6. 根據字形的步進象素增加筆位置;

7. 對剩餘的字形進行第三步;

8. 當所有字形都處理了,在新的筆位置設置文本光標。

注意字距調整不在這個算法中。

2、子象素定位
在渲染文本時使用子象素定位有時很有用。這非常重要,例如爲了提供半所見即所得的文本佈局,文本渲染的算法和上一節很相似,但是有些區別:

Ÿ 筆位置表示成小數形式的象素;

Ÿ 因爲將一個已經hint過的輪廓平移一個非整型距離將破壞網格對齊,字形原點的位置在渲染字符映象前必須取整;

Ÿ 步進寬度表示成小數形式的象素,沒有必要是整型。

這裏是算法的改進版本:

1. 將字符串轉換成一系列字形索引;

2. 將筆放置在光標位置,這可以是一個非整型點;

3. 獲得或裝入字形映象;

4. 平移字形以使它的原點匹配取整後的筆位置;

5. 將字形渲染到目標設備;

6. 根據字形的步進象素寬度增加筆位置,這個寬度可以是小數形式;

7. 對剩餘的字形進行第三步;

8. 當所有字形都處理了,在新的筆位置設置文本光標。

注意使用小數象素定位後,兩個指定字符間的空間將不是固定的,它右先前的取整操作堆積的數決定。

3、簡單字距調整
在基本文本渲染算法上增加字距調整非常簡單,當一個字距調整對發現了,簡單地在第4步前,將縮放後的調整距離增加到筆位置即可。淡然,這個距離在算法1需要被取整,算法2不必要。

4、自右向左佈局
佈局Arabic或Heberw文字的過程非常相似,區別只是在字形渲染前,筆位置需要減少(記住步進寬度總是正值)

5、垂直佈局
佈局垂直文字也是同樣的過程,重要的區別如下:

Ÿ 基線是垂直的,使用垂直的度量而不是水平度量;

Ÿ 左跨距通常是負的,但字形原點必須在基線上;

Ÿ 步進高度總是正值,所以筆位置必須減少以從上至下書寫;

6、所見即所得佈局


六、FT輪廓
1、FT輪廓描述和結構
a. 輪廓曲線分解
一個輪廓是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點,控制一個cubic Bezier弧


下面的規則應用於將輪廓點分解成線段和弧

兩個相鄰的“on”點表示一條線段;

一個conic Off點在兩個on點之間表示一個conic Bezier弧,off點是控制點,on點是起點和終點;

兩個相鄰的cubic off點在兩個on點之間表示一個cubic Bezier弧,它必須有兩個cubic控制點和兩個on點。

最後,兩個相鄰的conic off點強制??在它們正中間創建一個虛擬的on點。這大大方便定義連續的conic弧。TrueType規範就是這麼定義的。

注意,在單個輪廓線中可以混合使用conic和cubic弧,不過現在沒有那種字體驅動產生這樣的輪廓。



b. 輪廓描述符
FT輪廓通過一個簡單的結構描述

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是一組字節,用來存放每個輪廓的點標記。

2、邊界和控制框計算
邊 界框(bbox)是一個完全包含指定輪廓的矩形,所要的是最小的邊界框。因爲弧的定義,bezier的控制點無需包含在輪廓的邊界框中。例如輪廓的上緣是 一個Bezier弧,一個off點就位於bbox的上面。不過這在字符輪廓中很少出現,因爲大多字體設計者和創建工具都會在每個曲線拐點處放一個on點, 這會使hinting更加容易。於是我們定義了控制框(cbox),它是一個包含輪廓所有點的最小矩形,很明顯,它包含bbox,通常它們是一樣的。不想 bbox,cbox計算起來非常快。


控制框和邊界框可以通過函數FT_Outline_Get_CBox()和 FT_Outline_Get_BBox()自動計算,前者總是非常快,後者在有外界控制點的情況下會慢一點,因爲需要找到conic和cubic弧的末 端,如果不是這種情況,它和計算控制框一樣快。

注意,雖然大多字形輪廓爲易於hint具有相同的cbox和bbox,這在它們進行變換以後,如旋轉,就不再是這種情況了。

3、座標、縮放和網格對齊
輪廓點的向量座標表示爲26.6格式,即一個象素的1/64。因此,座標(1.0,-2.5)存放整型對(x:64,y:-192)。

在主字形輪廓從EM網格縮放到當前字符大小後,hinter負責對齊重要的輪廓點到象素網格。雖然這個過程很難幾句話說清楚,但是它的目的也就是取整點的位置,以保持字形重要的特性,如寬度、主幹等。下面的操作可以用來將26.6格式的向量距離取整到網格:

round(x) == (x + 32) & -64

floor(x) == x & -64

ceiling(x) == (x + 63) & -64

一旦一個字形輪廓經過對齊或變換,在渲染之前通常要計算字形的映象象素大小。做到這一點,必須考慮如下:

掃描線轉換器畫出所有中心在字形形狀中的象素,他也可以檢測drop-outs???

這導致如下的計算:

Ÿ 計算bbox;

Ÿ 對齊bbox如下:

xmin = floor(bbox.xMin)

xmax = ceiling(bbox.xMax)

ymin = floor(bbox.yMin)

ymax = ceiling(bbox.yMax)

Ÿ 返回象素尺寸,即

width = (xmax-xmin) / 64



height = (ymax-ymin) / 64

通過對齊bbox,可以保證所有的象素中心將畫到,包括那些從drop-out控制來的,將在調整後的框子之中。接着,框子的象素尺寸可以計算出來。

同時注意,當平移一個對齊的輪廓,應該總是使用整型距離來移動。否則,字形的邊緣將不再對齊象素網格,hinter的工作將無效,產生非常難看的位圖。

七、FT位圖
1、向量座標和象素座標對比
這裏闡述了向量座標的象素座標的區別,爲了更清楚的說明,使用方括號來表示象素座標,使用圓括號表示向量座標。

在象素座標中,我們使用Y軸向上的規約,座標[0,0]總是指位圖左下角象素,座標[width-1, rows-1]是右上角象素。在向量座標中,點座標用浮點單位表示,如(1.25, -2.3),這個位置並不是指一個特定象素,而是在2D平面上一個非實質性的點。

象 素其實在2D平面上是一個方塊,它的中心是象素座標值的一半,例如位圖的左下角象素通過方塊(0,0)-(1,1)界定,它的中心位於 (0.5,0.5)。注意這兒用的是向量座標表示。這對計算距離就會發生一些區別。例如,[0,0]-[10.0]一條線的象素長度是11,然而, (0,0)-(10,0)的向量程度覆蓋了10個象素,因此它的長度是10。


2、FT位圖和象素圖描述符
一個位圖和象素圖通過一個叫FT_Bitmap的單一結構描述,他定義在<freetype/ftimage.h>中,屬性如下

FT_Bitmap

rows 行數,即位圖中的水平線數

width 位圖的水平象素數

pitch 它的絕對值是位圖每行佔的字節數,根據位圖向量方向,可以是正值或是負值

buffer 一個指向位圖象素緩衝的無類型指針

pixel_mode 一個枚舉值,用來表示位圖的象素格式;例如ft_pixel_mode_mono表示1位單色位圖,ft_pixel_mode_grays表示8位反走樣灰度值

num_grays 這隻用於灰度象素模式,它給出描述反走樣灰度級別的級數,FT缺省值爲255。

pitch 屬性值的正負符號確定象素緩衝中的行是升序還是降序存放。上面說道FT在2D平面上使用Y軸向上的規約,這意味着(0,0)總是指向位圖的左下角。如果 picth是正值,按照向量減少的方向存儲行,即象素緩衝的第一個字節表示位圖最上一行的部分。如果是負值,第一個字節將表示位圖最下一行的部分。對所有 的情況,pitch可以看作是在指定位圖緩衝中,跳到下一個掃描行的字節增量。


通常都使用正pitch,當然有的系統會使用負值。



3、輪廓轉換到位圖和象素圖
使用FT從一個向量映象轉換到位圖和象素圖非常簡單,但是,在轉換前,必須理解有關在2D平面上放置輪廓的一些問題:

Ÿ 字形轉載器和hinter在2D平面上放置輪廓時,總將(0,0)匹配到字符原點,這意味着字形輪廓,及對應的邊界框,可以在平面中放置於任何地方。

Ÿ 目標位圖映射到2D平面上,左下角在(0,0)上,這就是說一個[w,h]大小的位圖和象素圖將被映射到(0,0)-(w,h)界定的2D矩形窗口。

Ÿ 當掃描轉換輪廓,所有在這個位圖窗口的部分將被渲染,其他的被忽略。

很多使用FT的開發者都會有個誤解,認爲一個裝入的輪廓可以直接渲染成一個適當大小的位圖,下面的圖像表明這個問題。

第一個圖表明一個2D平面上一個裝入的輪廓;

第二個表示一個任意大小[w,h]維護的目標窗口;

第三個表示在2D平面上輪廓和窗口的合併;

最後一個表示位圖實際被渲染的部分。


實際上,幾乎所有的情況,裝入或變換過的輪廓必須在渲染成目標位圖之前作平移操作,以調整到相對目標窗口的位置。

例如,創建一個單獨的字形位圖正確的方法如下:

Ÿ 計算字形位圖的大小,可以直接從字形度量計算出來,或者計算它的邊界框(這在經過變換後非常有用,此時字形度量不再有效)。

Ÿ 根據計算的大小創建位圖,別忘了用背景色填充象素緩衝;

Ÿ 平移輪廓,使左下角匹配到(0,0)。別忘了爲了hinting,應該使用整型。通常,這就是說平移一個向量(-ROUND(xMin), -ROUND(yMin))。

Ÿ 調用渲染功能,例如FT_Outline_Render()函數。

在將字形映象直接寫入一個大位圖的情況,輪廓必須經過平移,以將它們的向量位置對應到當前文本光標/字符原點上。

發佈了60 篇原創文章 · 獲贊 12 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章