freetype

URN Logo
UNIX Resources » Linux » China Linux Forum » Linux下的GUI軟件開發 » 14 » [精華] FreeType 2開發文檔 [中譯版]
 
 
announcement 聲明: 本頁內容爲中國Linux論壇的內容鏡像,文章的版權以及其餘全部的相關權利屬於中國Linux論壇和相應文章的做者,若是轉載,請註明文章來源及相關版權信息。
 
Resources
China Linux Forum(finished)
Linux Forum(finished)
FreeBSD China(finished)
linuxforum.net
  業界新聞與評論
  自由軟件雜談
  IT 人生
  Linux軟件快遞
  翻譯做坊
  Linux圖書與評論
  GNU Emacs/XEmacs
  Linux 中文環境和中文化
  Linux桌面與辦公軟件
  Linux 多媒體與娛樂版
  自由之窗Mozilla
  筆記本電腦上的Linux
  Gentoo
  Debian 一族
  網絡管理技術
  Linux 安裝與入門
  WEB服務器和FTP服務器
  域名服務器和郵件服務器
  Linux防火牆和代理服務器應用
  文件及打印服務器
  技術培訓與認證
  TI專版
  Linux內核技術
  Linux 嵌入技術
  Linux設備驅動程序
  Linux 集羣技術
  LINUX平臺數據庫
  系統和網絡安全
  CPU 與 編譯器
  系統計算研究所專欄
  Linux下的GUI軟件開發
  C/C++編程版
  PHP 技 術
  Java&jsp技術
  Shell編程技術
  Perl 編 程
  Python 編 程
  XML/Web Service 技術
  永遠的Unix
  FreeBSD世界
 
     
[精華] FreeType 2開發文檔 [中譯版]
[精華] FreeType 2開發文檔 [中譯版]  -   gogoliu  [ 2006-01-23 19:18  |   492 byte(s)]
 
[精華] Re: FreeType 2開發文檔 [中譯版]  -   gogoliu  [ 2006-01-23 19:18  |   25,574 byte(s)]
 
Re: FreeType 2開發文檔 [中譯版]  -   gogoliu  [ 2006-03-13 23:39  |   37,333 byte(s)]
 
[精華] Re: FreeType 2開發文檔 [中譯版]  -   gogoliu  [ 2006-01-23 19:14  |   15,143 byte(s)]
 
[精華] Re: FreeType 2開發文檔 [中譯版]  -   gogoliu  [ 2006-01-23 19:06  |   24,274 byte(s)]
 
Subject: [精華] Re: FreeType 2開發文檔 [中譯版] 
Author: gogoliu    Posted: 2006-01-23 19:06    Length: 24,274 byte(s)
[Original] [Print] [Top]
《FreeType Glyph Conventions》中譯版 

FreeType字形約定 

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

分辨率用dpi表示,由於水平和垂直分辨率能夠不一樣,單個點數一般定義不一樣象素文本寬度和高度。 

二、向量表示 
字體輪廓的源格式是一組封閉的路徑,叫作輪廓線。每一個輪廓線劃定字形的外部或內部區域,它們能夠是線段或是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正方形顯示的大小,因此不一樣字體,雖然一樣大小,可是它們的高度可能不一樣。 

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

因此,優秀的字形渲染過程在縮放「點」是,須要經過一個網格對齊(grid-fitting)的操做(一般叫hinting),將它們對齊到目標設備的象素網格。這主要目的之一是爲了確保整個字體中,重要的寬度和高度可以一致。例如對於字符I和T來講,它們那個垂直筆劃要保持一樣象素寬度。另外,它的目的還有管理如stem和overshoot的特性,這在小象素字體會引發一些問題。 

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

* 顯式網格對齊 

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

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

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

* 自動網格對齊 

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

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

方案 優勢 缺點 

顯式 質量:對小字體有很好的結果,這對屏幕顯示很是重要。 速度:若是程序很複雜,解釋字節碼很慢 
一致性:全部渲染器產生一樣的字形位圖。 大小:字形程序會很長。 
技術難度:編寫優秀的hinting程序很是難,沒有好的工具支持。 

隱式 大小:Hint一般比顯式字形程序小的多 質量:小字體很差,最後結合反走樣 
速度:網格對齊會很是快 不一致:不一樣渲染器結果不一樣,甚至同一引擎不一樣版本也不一樣。 

自動 大小:不須要控制信息,致使更小的字體文件 質量:小字體很差,最後結合反走樣 
速度:依賴對齊算法,一般比顯式對齊快。 速度:依賴算法 
不一致:不一樣渲染器結果不一樣,甚至同一引擎不一樣版本也不一樣。 


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

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

* 對水平佈局,字形簡單地擱在基線上,經過增長筆位置來渲染文本,既能夠向右也能夠向左增長。 

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

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


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


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

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

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

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

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

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

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

internal leading = ascent – descent – EM_size 

* 外部leading。行距的別名。 

三、跨距(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) 

下圖是水平佈局全部的度量 


下圖是垂直佈局的度量 


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

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

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

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

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

這有一些含義以下, 

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

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

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

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

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

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

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

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

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


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

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

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


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


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

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

* 依據佈局或書寫,字距能夠表示水平或垂直方向。 

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

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

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


根據字符的外形,有時候須要在點和隨後的字符間做附加的調整,當應用「標準」的字距調整,上面的句子以下 


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

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

1. 在基線上放置第一個字形; 
2. 將筆位置保存到pen1; 
3. 根據第一個和第二個字形的字距距離調整筆位置; 
4. 放置第二個字形,並計算下個筆位置,放到pen2; 
5. 若是pen1大於pen2,使用pen1做爲下個筆位置,不然使用pen2。 


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

1. 將字符串轉換成一系列字形索引; 
2. 將筆放置在光標位置; 
3. 得到或裝入字形映象; 
4. 平移字形以使它的原點匹配筆位置; 
5. 將字形渲染到目標設備; 
6. 根據字形的步進象素增長筆位置; 
7. 對剩餘的字形進行第三步; 
8. 當全部字形都處理了,在新的筆位置設置文本光標。 

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

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

* 筆位置表示成小數形式的象素; 
* 由於將一個已經hint過的輪廓平移一個非整型距離將破壞網格對齊,字形原點的位置在渲染字符映象前必須取整; 
* 步進寬度表示成小數形式的象素,沒有必要是整型。 

這裏是算法的改進版本: 

1. 將字符串轉換成一系列字形索引; 
2. 將筆放置在光標位置,這能夠是一個非整型點; 
3. 得到或裝入字形映象; 
4. 平移字形以使它的原點匹配取整後的筆位置; 
5. 將字形渲染到目標設備; 
6. 根據字形的步進象素寬度增長筆位置,這個寬度能夠是小數形式; 
7. 對剩餘的字形進行第三步; 
8. 當全部字形都處理了,在新的筆位置設置文本光標。 

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

三、簡單字距調整 
在基本文本渲染算法上增長字距調整很是簡單,當一個字距調整對發現了,簡單地在第4步前,將縮放後的調整距離增長到筆位置便可。淡然,這個距離在算法1須要被取整,算法2沒必要要。 

四、自右向左佈局 
佈局Arabic或Heberw文字的過程很是類似,區別只是在字形渲染前,筆位置須要減小(記住步進寬度老是正值) 

五、垂直佈局 
佈局垂直文字也是一樣的過程,重要的區別以下: 

* 基線是垂直的,使用垂直的度量而不是水平度量; 
* 左跨距一般是負的,但字形原點必須在基線上; 
* 步進高度老是正值,因此筆位置必須減小以從上至下書寫; 

六、所見即所得佈局 


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

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


控制框和邊界框能夠經過函數FT_Outline_Get_CBox()和 FT_Outline_Get_BBox()自動計算,前者老是很是快,後者在有外界控制點的狀況下會慢一點,由於須要找到conic和cubic弧的末端,若是不是這種狀況,它和計算控制框同樣快。 

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

三、座標、縮放和網格對齊 
輪廓點的向量座標表示爲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的工做將無效,產生很是難看的位圖。 


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

在象素座標中,咱們使用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。 


二、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,固然有的系統會使用負值。 

三、輪廓轉換到位圖和象素圖 
使用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()函數。 

在將字形映象直接寫入一個大位圖的狀況,輪廓必須通過平移,以將它們的向量位置對應到當前文本光標/字符原點上。 
 
----
良好的溝通能力 和 積極的行動 是成功的鑰匙。
[Original] [Print] [Top]
Subject: [精華] Re: FreeType 2開發文檔 [中譯版] 
Author: gogoliu    Posted: 2006-01-23 19:14    Length: 15,143 byte(s)
[Original] [Print] [Top]
《The design of FreeType 2》中譯版 

FreeType 2的設計 

介紹 
這份文檔提供了FreeType 2函數庫設計與實現的細節。本文檔的目標是讓開發人員更好的理解FreeType 2是如何組織的,並讓他們擴充、定製和調試它。 
首先,咱們先了解這個庫的目的,也就是說,爲何會寫這個庫: 
* 它讓客戶應用程序方便的訪問字體文件,不管字體文件存儲在哪裏,而且與字體格式無關。 
* 方便的提取全局字體數據,這些數據在日常的字體格式中廣泛存在。(例如:全局度量標準,字符編碼/字符映射表,等等) 
* 方便的提取某個字符的字形數據(度量標準,圖像,名字,其餘任何東西) 
* 訪問字體格式特定的功能(例如,SFNT表,多重控制,OpenType輪廓表) 
Freetype 2的設計也受以下要求很大的影響: 
* 高可移植性。這個庫必須能夠運行在任何環境中。這個要求引入了一些很是激烈的選擇,這些是FreeType2的低級系統界面的一部分。 
* 可擴展性。新特性應該能夠在極少改動庫基礎代碼的前提下添加。這個要求引入了很是簡單的設計:幾乎全部操做都是以模塊的形式提供的。 
* 可定製。它應該可以很容易創建一個只包含某個特定項目所需的特性的版本。當你須要集成它到一個嵌入式圖形庫的字體服務器中時,這是很是重要的。 
* 簡潔高效。這個庫的主要目標是隻有不多cpu和內存資源的嵌入式系統。 
這份文檔的其餘部分分爲幾個部分。首先,一些章節介紹了庫的基本設計以及Freetype 2內部對象/數據的管理。 
接下來的章節專一於庫的定製和與這個話題相關的系統特定的界面,如何寫你本身的模塊和如何按需裁減庫初始化和編譯。 


1、組件和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() 


2、公共對象和類 


一、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對象。 

在實際使用中,能夠進行類型轉換。 

後面 

二、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的大小是在編譯時動態肯定的。) 

三、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定義。 

四、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對象中。 

五、FT_GlyphSlot類 
字形槽的目的是提供一個地方,能夠很容易地一個個地裝入字形映象,而無論它的格式(位圖、向量輪廓或其餘)。理想的,一旦一個字形槽建立了,任何字形映象能夠裝入,無需其餘的內存分配。在實際中,只對於特定格式才如此,像TrueType,它顯式地提供數據來計算一個槽地最大尺寸。 

另外一個字形槽地緣由是他用來爲指定字形保存格式特定的hint,以及其餘爲正確裝入字形的必要數據。 

基本的FT_GlyphSlotRec結構體只向客戶程序展示了字形metics和映象,而真正的實現回包含更多的數據。 

例如,TrueType特定的TT_GlyphSlotRec結構包含附加的屬性,存放字形特定的字節碼、在hint過程當中暫時的輪廓和其餘一些東西。 

最後,每一個外觀對象有一個單一字形槽,能夠用face->glyph直接訪問。 

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

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

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

七、對象關係 


3、內部對象和類 
一、內存管理 
全部內存管理操做經過基礎層中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手工註冊你的驅動。 

二、輸入流 
字體文件老是經過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在打開狀態不是一個很好的主意。 

三、模塊 
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對象,由於這會致使不預期的對象刪除。 

四、庫 
如今來講說FT_Library對象,如上所述,一個庫實例至少包含以下: 

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

* 一個FT_Module對象列表,對應「已安裝」或「已註冊」的模塊,它能夠隨時經過FT_Add_Module()和FT_Remove_Module()管理; 

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

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

光柵池是一個固定大小的一塊內存,爲一些內存須要大的操做做爲內部的「草稿區域」,避免內存分配。例如,它用在每一個渲染器轉換一個向量字形輪廓到一個位圖時(這其實就是它爲什麼叫光柵池的緣由)。 

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

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

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


4、模塊類 

在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字符解碼的函數,type一、cid和cff都須要這個特性 


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

咱們如今來學習模塊是如何描述以及如何被FreeType2庫管理的。 
1 FT_Module_Class結構 


2 FT_Module類型 

 
----
良好的溝通能力 和 積極的行動 是成功的鑰匙。
[Original] [Print] [Top]
Subject: [精華] FreeType 2開發文檔 [中譯版] 
Author: gogoliu    Posted: 2006-01-23 19:18    Length: 492 byte(s)
[Original] [Print] [Top]
下面3份文檔是FreeType 2文檔的中譯版,包括《FreeType Glyph Conventions》、《The design of FreeType 2》、和《The FreeType 2 Tutorial》。其中,前二者是在網上找到的,譯者不詳,而且有些部分沒有翻譯。我翻譯了第三份文檔的第一部分,因爲翻譯水平低,翻譯得很死板、機械,你們湊合着看。第二部分要是沒人翻譯的話,我會在年後翻譯。這3份文檔的中譯版都有待完善,但願有空餘時間和能力的朋友能夠幫忙完善它們。  
----
良好的溝通能力 和 積極的行動 是成功的鑰匙。
[Original] [Print] [Top]
Subject: [精華] Re: FreeType 2開發文檔 [中譯版] 
Author: gogoliu    Posted: 2006-01-23 19:18    Length: 25,574 byte(s)
[Original] [Print] [Top]
《The FreeType 2 Tutorial》第一部分中譯版 

FreeType 2 教程 

第一步 -- 簡易的字形裝載 

介紹 

這是「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; /* 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; /* face對象的句柄 */ 


error = FT_Init_FreeType( &library ); 
if ( error ) { ... } 

error = FT_New_Memory_Face( library, 
buffer, /* 緩存的第一個字節 */ 
size, /* 緩存的大小(以字節表示) */ 
0, /* face索引 */ 
&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, /* face對象的句柄 */ 
0, /* 以1/64點爲單位的字符寬度 */ 
16*64, /* 以1/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, /* 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, /* 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, /* 目標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 ) { ... } 

/* 如今,選擇face對象的字符表*/ 
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, /* 目標face對象 */ 
&matrix, /* 指向2x2矩陣的指針 */ 
&delta ); /* 指向2維矢量的指針 */ 

這個函數將對指定的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; 

/* 如今,繪製到咱們的目標表面(surface) */ 
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; /* 忽略錯誤 */ 

/* 如今,繪製到咱們的目標表面(surface) */ 
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 ); 

/* 26.6 笛卡兒空間座標中筆的位置,以(300, 200)爲起始 */ 
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的基礎以及渲染旋轉文字的充分知識。 

下一部分將深刻了解FreeType 2 API更詳細的資料,它可讓你直接訪問字形度量標準和字形圖像,還能讓你學習到如何處理縮放、hinting、自居調整,等等。 

第三部分將討論模塊、緩存和其餘高級主題,如如何在一個face中使用多個尺寸對象。【這部分尚未編寫】
 
----
良好的溝通能力 和 積極的行動 是成功的鑰匙。
[Original] [Print] [Top]
Subject: Re: FreeType 2開發文檔 [中譯版] 
Author: gogoliu    Posted: 2006-03-13 23:39    Length: 37,333 byte(s)
[Original] [Print] [Top]
《The FreeType 2 Tutorial》第二部分中譯版 

FreeType 2 教程 

第二步 -- 管理字形 

介紹 

這是「FreeType2 教程」的第二部分。它將教會你如何: 

* 檢索字形度量 
* 容易地管理字形圖像 
* 檢索全局度量(包括字距調整) 
* 渲染一個簡單的字符串(採用字距調整) 
* 渲染一個居中的字符串(採用字距調整) 
* 渲染一個變換的字符串(採用居中) 
* 在須要時以預設字體單位的格式獲取度量,以及把它們縮放到設備空間 

1.字形度量 

從字形度量這個名字能夠想到,字形度量是關聯每個字形的肯定距離,以此描述如何使用該距離來排版文本。 

一般一個字形有兩個度量集:用來排版水平文本排列的字形(拉丁文、西里爾文、阿拉伯文、希伯來文等等)和用來排版垂直文本排列的字形(中文、日文、韓文等等)。 

要注意的是隻有不多的字體格式提供了垂直度量。你能夠使用宏FT_HAS_VERTICAL測試某個給出的face對象是否包含垂直度量,當結果爲真時表示包含。 

個別的字形度量能夠先裝載字形到face的字形槽,而後經過face->glyph->metrics結構訪問,其類型爲FT_Glyph_Metrics。咱們將在下面詳細討論它,如今,咱們只關注該結構包含以下的字段: 

Width 
這是字形圖像的邊框的寬度。它與排列方向無關。 

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是下面字段之一: 

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包含垂直度量時這個值纔是可靠的。 

2.管理字形圖像 

轉載到字形槽得字形圖像能夠轉換到一幅位圖中,這能夠在裝載時使用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' ... 

/* 複製 glyph 到 glyph2 */ 

error = FT_Glyph_Copy( glyph, &glyph2 ); 
if ( error ) { ... 沒法複製(內存不足) ... } 

/* 平移 `glyph' */ 

delta.x = -100 * 64; /* 座標是 26.6 象素格式的 */ 
delta.y = 50 * 64; 

FT_Glyph_Transform( glyph, 0, &delta ); 

/* 變換 glyph2 (水平剪切) */ 

matrix.xx = 0x10000L; 
matrix.xy = 0.12 * 0x10000L; 
matrix.yx = 0; 
matrix.yy = 0x10000L; 

FT_Glyph_Transform( glyph2, &matrix, 0 ); 

注意,2x2矩陣變換老是適用於字形的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; /* 26.6格式的1/2象素 */ 
origin.y = 0; 
error = FT_Glyph_To_Bitmap( 
&glyph, 
render_mode, 
&origin, 
1 ); /* 銷燬原始圖像 == true */ 

一些註解: 

* 第一個參數是源字形句柄的地址。當這個函數被調用時,它讀取該參數來訪問源字形對象。調用結束後,這個句柄將指向一個新的包含渲染後的位圖的字形對象。 

* 第二個參數時一個標準渲染模式,用來指定咱們想要哪一種位圖。它取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字段。 

3.全局字形度量 

不一樣於字形度量,全局度量是用來描述整個字體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, /* 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是用來返回預設字距調整距離,它以字體單位的格式表示。你能夠在稍後用本部分的最後一章描述的算式把它拉伸到設備空間。 

注意,「左」和「右」位置是指字符串字形的可視順序。這對雙向或由右到左的文原本說是很重要的。 

4.簡單的文本渲染:字距調整+居中 

爲了顯示咱們剛剛學到的知識,如今咱們將示範如何修改第一部分給出的代碼以渲染一個字符串,而且加強它,使它支持字距調整和延遲渲染。 

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; /* 忽略錯誤 */ 

/* 如今繪製到咱們的目標表面(surface) */ 
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; /* 以 (0,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爲「空」值 */ 
bbox.xMin = bbox.yMin = 32000; 
bbox.xMax = bbox.yMax = -32000; 

/* 對於每個字形圖像,計算它的邊界框,平移它,而且增長字符串bbox */ 
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; 
} 

/* 檢查咱們是否真的增長了字符串bbox */ 
if ( bbox.xMin > bbox.xMax ) 
{ 
bbox.xMin = 0; 
bbox.yMin = 0; 
bbox.xMax = 0; 
bbox.yMax = 0; 
} 

/* 返回字符串bbox */ 
*abbox = bbox; 
} 

最終獲得的邊界框尺寸以整數象素的格式表示,而且能夠隨後在渲染字符串前用來計算最終的筆位置,以下: 

/* 計算整數象素表示的字符串尺度 */ 
string_width = string_bbox.xMax - string_bbox.xMin; 
string_height = string_bbox.yMax - string_bbox.yMin; 

/* 計算以26.6笛卡兒象素表示的筆起始位置*/ 
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; /* 以 (0,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; 

/* 獲取原始字形序列的 bbox */ 
compute_string_bbox( &string_bbox ); 

/* 計算整數象素表示的字符串尺度 */ 
string_width = (string_bbox.xMax - string_bbox.xMin) / 64; 
string_height = (string_bbox.yMax - string_bbox.yMin) / 64; 

/* 設置26.6笛卡兒空間表示的筆起始位置 */ 
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 ); /* 銷燬 "image" 指向的副本 */ 
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象素格式表示的距離,以下所示: 

/* 把預設距離轉換到1/64象素 */ 
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爲基礎構建一個至關好的文字服務,並且要是你願意,你能夠在這裏止步了。 

下一部分將涉及FreeType2的內部(例如模塊,矢量輪廓,設備驅動器,渲染器),以及少數的字體格式特有的問題(主要是,如何訪問某些TrueType或Type 1表)。【這部分尚未編寫】 

 
----
killall 眼高手低 用心浮躁 淺嘗輒止
[Original] [Print] [Top]
« Previous thread 
Qt/Qtopia中文論壇開張了
Linux下的GUI軟件開發 
14
Next thread » 
關於UNICODE的問題? 麻煩高手指教!!
       

Copyright © 2007~2009 UNIX Resources Network, All Rights Reserved.      About URN | Privacy & Legal | Help | Contact us
 
webmaster: webmaster@unixresources.net
This page created on 2009-09-07 16:42:31, cost 0.0448169708252 ms.
相關文章
相關標籤/搜索