TrueType入門:基本概念

本文做者李鬆峯,資深技術圖書譯者,翻譯出版過40餘部技術及交互設計專著,現任360奇舞團Web前端開發資深專家,360前端技術委員會委員、W3C AC表明前端

OpenType是TrueType的擴展。本文全流程介紹TrueType從字體設計到字體顯示的每一個步驟,這些步驟一樣也適用於OpenType。算法

TrueType字體可能誕生於紙上,也可能從其餘格式轉換而來。但最終,字體文件中必定包含每一個字形的描述信息。下圖展現了從設計稿原件到數字化字形,再到字體文件中數字化輪廓的過程。網絡

圖1 設計稿原件、數字化字形、字體文件中以FUnit 座標表示的數字化輪廓數據結構

那麼TrueType字體是如何在顯示設備(屏幕、打印機)上應用的呢?ide

首先,要將字體文件中存儲的輪廓縮放到要求的尺寸,即將字體文件中以FUnit(Font Uint,字體單位)表示的原始輪廓轉換爲特定於設備的像素座標。函數

而後,解釋器運行與字形關聯的指令,運行指令的結果是完成字形的網格適配(grid-fit)。完成網格適配,再由掃描轉換程序生成最終在目標設備上呈現的位圖圖像。性能

圖2 字體渲染流程測試

1,在TrueType字體文件中以FUnit座標形式描述字形的輪廓 2,縮放程序將FUnit轉換爲像素座標,並縮放至應用程序要求的大小 3,輪廓縮放至新網格 4,縮放後以像素座標表示的輪廓 5,解釋程序執行與字形B關聯的指令,進行網格適配 6,網格適配後的輪廓 7,網格適配後的輪廓 8,掃描轉換器決定打開哪些像素 9,在目標設備上渲染位置字體

字形數字化

字體中所包含字形的輪廓是以連續的點來表示的,這些點位於一個虛擬的座標系中。翻譯

輪廓

在TrueType字體中,字形的形狀由輪廓(contour)來表示,而輪廓由位於虛擬座標上的點來表示。簡單的字形可能只有一個輪廓,複雜的字形則有多個輪廓。而複合字形則能夠由多個簡單的字形組合而成。字體文件中,那些沒有可見外形的控制字符都會映射到沒有輪廓的字形。

圖3 由一系列線上點和線下點構成的字形

構成曲線的點必須以連續的數值編號,編號是升序仍是降序也很重要,由於它決定構成字形的形狀的填充模式。總之,若是曲線是沿着升序編號定義,則黑空間即填充區始終在右側。

FUnit與em方塊

在TrueType字體文件中,點的位置以字體單位或FUnit表示。所謂FUnit,就是em方塊中最小的度量單位,而em方塊則是用於衡量字形大小以及對齊字形的一個虛擬方塊。一般,一個字形的em方塊包含字形的全身長,再加上排版時沒有額外鉛空條件下避免行與行過於擁擠的額外空間。

圖5 em方塊

在金屬字模的時代,字形不會伸出em方塊,但數字字體沒有這些限制。em方塊能夠作得足夠大,以包含全部字形,包括註音的字形。不過,在必要的時候,有的字形也能夠伸出em方。TrueType字體支持這兩種情形,具體使用哪一種由字體制造商決定。

圖6 伸出em方塊的字形

em方塊定義了一個二維座標網絡系統,其中x軸表示水平方向的移動,y軸表示垂直方向的移動。

FUnit與網格

字體數字化,首先要肯定描述字形輪廓的點,其精確度或者解析度如何。這些點的精確度由em網格最小的可度量長度,也就是FUnit決定。這個網格在一個二維的座標系裏,座標原點是(0, 0)。但這個網格不是無窮平面,而是位於-16384到+16384 FUnit之間。FUnit大小的選擇不一樣,網格中點的數量也多寡不一樣

每一個em網格中包含的FUnit數量,也就是upem(FUnit per Em)由字體設計者或製造商決定。不過,將upem設定爲2的整數次冪,好比2048,可讓輪廓縮放時速度最快。

圖7 em方塊中的網格座標系

em方塊中的座標原點放在哪裏並無必定之規。實踐中,一般要遵循一些慣例。好比,Roman字體用於水平排版,那麼其y座標的0值一般放在字體的基線上。至於x座標的0值放在哪裏就很自由了。只不過選擇慣常的作法有可能性能更好。

好比,對於用於豎排版的字體,選擇字形的視覺中心點做爲x軸原點可讓字形排列起來更美觀。而用於水平排版的字體,也可讓字體輪廓最左端那個點的x值等於字形左邊空(left-side-bearing)。這樣的字體在PostScript打印機中打印速度更快。

圖8 兩種字形原點選擇:左邊是字形左邊空的x值爲0 右邊是視覺中心的x值爲0

接下來,每一個em方塊中包含的FUnit數量也就是upem決定了em方塊的粒度。upem越大,精度越高。

圖9 兩個em方塊的網格:左側每em包含8個單位 右側每em包含16個單位

FUnit是一個相對單位,由於其實際表明的大小會隨着em方塊的大小而變化。但每em中包含的FUnit數量,即upem則一個常量,不管字形最終被渲染爲多大都不會變。說到最終渲染,就又有了一個概念:ppem,即每em方塊對應的點數。這裏的點(point)是一個絕對大小單位,即1點=1/72英寸=0.353毫米。若是一個字形被渲染爲9點大,那麼每一個em方塊就包含9點(9×0.353=3.17毫米),被渲染爲14點大,每一個em方塊就是包含14點(14×0.353=4.94毫米)。既然upem不變,而ppem會變,那麼FUnit在渲染時對應的絕對大小天然也會變化。

圖10 72點的M和127點的M及它們的em方塊 兩種狀況下的upem都是8

換句話說,不管字體被渲染爲多大,以FUint爲單位的upem始終不會變

縮放字形

下面咱們來看看字體文件中的字形輪廓是怎麼縮放爲應用程序所要求的大小的。

FUnit到像素的轉換

FUnit轉換爲像素有兩個值須要計算:一是以FUint表示的字形轉換後至關於多少像素,二是FUnit座標系中的點轉換爲像素座標系中的點以後的座標是多少。

對於第一個轉換,最終的像素值取決於渲染爲多少點、目標設備的分辨率dpi和em方塊的FUnit即upem。假設字體文件中的字形寬度爲550 FUnit,設備分辨率爲72ppi,字體的upem爲2048,渲染爲18點,那麼渲染後字形的像素值爲:

550×18×72ppi/(72dpi×2048upem) = 4.83px

換句話說,字形轉換後的像素值與其自己的FUnit值、渲染的點數和設備分辨率成正比,與字體的upem成反比。最後,72dpi是一個常量(即1英寸等於72點)。

對於第二個轉換,即字形中點的座標從FUnit轉換爲像素,首先須要知道轉換後每em中點的數量,即ppem(與設備分辨率成正比):

ppem = 渲染點數 × 設備分辨率 / 72dpi

假設要打印爲12像素,而打印分辨率爲300dpi,那麼每一個字形的ppem就是:12 × 300 / 72 = 50。也就是每一個字形能夠用50個(墨)點來呈現。

而後,咱們知道有如下等式:

像素座標/ppem = FUnit座標/upem

也就是像素座標與ppem的比,始終等於FUnit座標與upem的比。因此:

像素座標 = FUnit座標 × ppem / upem

假設仍是在300dpi的激光打印機上,要打印12點大小的字形,那麼其ppem是50。相應地,這個字形輪廓中某個點的座標若是是(1024, 0),而字體的upem爲2048,則這個點對應的像素座標就是(25, 0)。

字形輪廓適配網格

適配網格是經過執行指令實現的。適配網格的目的是讓字形在不一樣大小和不一樣設備上都能保留或者呈現原始設計的特徵,特別是保證一致的主幹高度、均勻的間隔,以及消除像素漏點(dropout)。

爲實現這些目標,就要確保將字形像素化時打開某些像素。在這種狀況下,可能就須要改變或扭曲原始的輪廓定義以產生高質量的位圖。原始字形輪廓的這種變形就叫作網格適配(grid-fitting)。

下圖展現了網格適配對原始設計的拉伸效果:

圖11 未經網格適配和通過網格適配的輪廓及位圖

網格適配就是根據與字形關聯的指令拉伸字形的過程。通過網格適配後,構成字形輪廓的點數不變,但這些點的位置(座標)會發生偏移。

理解指令

TrueType指令集定義了不少指令供設計者使用,以指定如何渲染字形,好在被縮放時保留字體應有的特徵。換句話說,指令用於在爲了避免同大小或設備適配網格時控制字形輪廓。

適配網格意味着移動輪廓上的點,移動的點稱爲"動過"。使用TrueType字體不必定非要執行指令,若是輸出設備的分辨率夠高、渲染點數也夠大,不運行指令也能夠輸出高質量的字形。不過,對於渲染點數較小的狀況,爲保證輸出結果的可讀性,添加並運行指令則是相當重要的。

TrueType解釋器怎麼知道如何拉伸字形輪廓以產生能夠接受的結果呢?相關信息包含在附加給字體中每一個字形的指令裏。指令規定了字形在被縮放時須要保留的設計。下圖展現了在未運行指令的狀況下渲染9點大小的Arial小寫字母m時,因爲主幹要保持在像素中心點,結果會丟掉一條主幹。而右側的圖代表,指令將主幹與網格對齊,從而保持了主幹的完好無損。

圖12 應用指令先後的9點大小的Arial

TrueType解釋器

指令由TrueType解釋器解釋執行。具體來講,解釋器會處理指令流或指令序列,而指令會從解釋器的棧空間取得參數,而後將執行結果再放回到棧上。也有少數指令負責把數據推到棧上,這些指令從指令流中取得本身的參數。

解釋器的全部操做都在Graphics State的上下文中運行。Graphic State是一組變量,這些變量的值用於指導解釋器運行並決定特定指令執行的結果。

解釋器的操做能夠總結以下:

1,解釋器從指令流中取得一條指令,即一連串有序指令操做碼和數據。操做碼以字節爲單位,數據多是單字節也多是雙字節(字)。指令從指令流中取得字數據,也會建立雙字節的字。高字節先出如今指令流中,低字節緊隨其後。 2,執行指令:若是是推送指令,則從指令流中取得參數。其餘指令則從棧中取得數據,而這些指令生成的數據又會推送到棧上。解釋器的棧是一個後進先出的數據結構。指令都是從棧的最後一項取得本身須要的數據。指令集中包含對入棧、出棧、清空和複製棧的所有指令。指令執行的效果取決於Graphics State中的變量和值。而指令也能夠修改Graphics State的變量。 3,重複以上過程,直至全部指令都執行完畢。

指令在哪

指令可能保存在字體文件的不少地方。好比,能夠出如今Font Program和CVT Program中,也能夠出如今字形數據裏。位於前兩個表中的指令適用於整個字體,位於字形數據裏的指令只適用於特定的字形。

Font Program中的指令只會在應用程序讀取字體時執行一次。這裏的指令用於建立函數定義(FDEF)和指令定義(IDEF)。Font Program中的函數和指令定義可在字體文件中任何地方使用。

CVT Program中的指令會在每次字形縮放時執行,但它只用於字體層面的變化,而無論具體字形。具體來講,CVT Program中的指令用於創建Control Value Table中的值。Control Value Table也就是CVT的目的是輔助運行指令時維護字體的一致性。

引用CVT中值的指令稱爲間接指令,從字形數據中取得值的指令則是直接指令。TrueType字體文件中的CVT值以FUnit表示。當輪廓從FUnit轉換爲像素時,CVT中的值也會轉換。

向CVT中寫入值時,可使用字形座標系中的值,也可使用原始FUnit的值。解釋器會相應地縮放這些值。而從CVT中讀取的值始終都以像素爲單位。

Graphics State

Graphics State包含一個表,其中保存着變量及它們的值。全部指令都是在Graphics State的上下文中運行。Graphics State中的全部變量都有默認值,其中一些值能夠在CVT Program中修改。可是,在處理單個字形時修改Graphics State變量的值只會對影響相應字形的後續處理。

掃描轉換程序

TrueType的掃描轉換程序接收字形的輪廓,生成該字形的位圖圖像。掃描轉換程序有兩種模式。在第一種模式下,掃描轉換程序使用一個簡單的算法肯定哪些像素屬於字形輪廓。

  • 規則一:若是像素的中心落在字形的輪廓上,則該像素打開併成爲字形的一部分。
  • 規則二:若是輪廓剛好落在一個像素的中心,則該像素打開。

若是一個點具備不是零的「纏繞」(winding)值,則該點就被認爲是字形內的點。要肯定一個點的「纏繞」值,須要從這個點向無窮大方向畫一條放射線。從0開始,每當有一個輪廓從右向左或從下向上跨過這條放射線,就減1。這種交叉稱爲「開轉換」(on transition)。反之,每當有一個輪廓從左向右或從上向下跨過這條放射線,就加1。這種交叉稱爲「關轉換」(off transition)。

至於輪廓的方向,能夠經過查看點的編號肯定。方向始終都從小號到大號。下圖說明了如何使用纏繞值來肯定一個點是否是在字形內部。

圖13 肯定一個點的纏繞值

圖中點p1一共經歷了4次轉換(開、關、開、關),由於轉換次數是偶數,因此纏繞值爲0,換句話說該點不是字形內的點。而p2經歷了3次轉換(關、開、關),纏繞值爲+1,所以點p2是字形內的點。

消除漏點

漏點(dropout)會在字形內部相連部分包含兩個黑像素但經過只能鏈接黑像素的直線卻不能鏈接時發生。

圖14 字母m有兩個漏點

TrueType指令的設計可讓掃描轉換程序開啓必要的像素,而沒必要去管渲染大小或者如何變換。可是,咱們畢竟不可能預先知道字形所要發生的全部變換。特別是在ppem較小而字形相對複雜的狀況下,像素漏點在所不免。

經過觀察鏈接兩個相鄰像素中心的線段能夠測試漏點。若是這個線段同時與一個開轉換和一個關轉換相交,則有出現漏點的可能。可是,只有兩個輪廓繼續沿各自方向前進,並切斷了鏈接相鄰像素中心的其餘線段時,潛在的漏點纔會成爲真正的漏點。若是兩個輪廓在跨過線段後立刻結合,則不會出現漏點,但字形的某個主幹可能會變短。

爲了不發生像素漏點,字體制造商可讓掃描轉換程序額外使用兩個規則。

  • 規則三:若是兩個相鄰像素中心之間的一條線(無論垂直仍是水平)同時與一個開轉換和一個關轉換輪廓相交,且沒有任何像素被規則一和規則二打開,則打開最右邊的像素(水平線段)或最下方的像素(垂直線段)。
  • 規則四:只在兩條輪廓線繼續沿各自方向分別與其餘線段相交時應用規則三。這樣不會對「短線」(stub)開啓像素。

字體制造商或設計者能夠選擇基於規則一和規則二進行簡單的掃描轉換,也能夠在必要時使用規則三和規則四。這些選擇形成了字體與字體間的差別。但prePromgram中的指令默認會影響整個字體,而個別字形中的變化則隻影響該字形。

相關連接

TrueType fundamentals : http://docs.microsoft.com/zh-cn/typography/opentype/spec/ttch01

關注咱們

相關文章
相關標籤/搜索