在上期文章裏,咱們介紹了字體文件的結構,也就是一系列的二進制表。在這一期裏,咱們關注的是這件事情:計算機中的數據是怎麼表達文字形狀的呢?讓咱們從點和線的概念開始吧:html
咱們已經知道,TTF 及其以後的字體標準,都支持對文字的平滑縮放。文字放大後那光滑的曲線當然是很是優美的,但它是怎樣在計算機中表達的呢?對這一問題最基礎的科普,只須要九年義務教育的知識就足夠理解了。git
在初中數學裏咱們就知道,過兩點便可肯定一條直線。那麼曲線呢?咱們能夠從最易於計算的二次曲線開始。步驟是這樣的:canvas
看着描述有些複雜?一圖勝千言:app
這和數學老師之前教的有什麼不同呢?最大的不一樣在於,和數學題裏常見「給曲線上的三個點,求出曲線表達式」的套路不一樣,咱們選擇的 p1 是在曲線以外(所謂 off-curve)的。這時曲線套的公式並非 y= ax^2 + bx + c
的形式,而是給定一個新參數 t,求出點座標 p(t) 的表達式——這就是所謂的二次貝塞爾曲線了。這種表達形式能消除掉一些你在作證實題時須要考慮的特殊狀況,所以特別適合優(偷)雅(懶)的工程實現。ide
觸類旁通地說,再複雜的曲線,均可以經過多段二次曲線的鏈接來造成。這時候,讓曲線「看起來平滑」的關鍵,在於兩條曲線交點處一階導數的連續性。看圖說話,大概就是這樣:工具
是否是有些 Photoshop 裏鋼筆工具的感受?不難發現,保證曲線鏈接處平滑的關鍵,就在於保證 p1 p2 p3 處在同一條直線上。若是整條曲線都知足這個條件,那麼 p2 實際上是多餘的,只須要下面的形式就足夠了:post
像這樣,咱們只依靠兩個 off-curve 的控制點,就能把兩段二次曲線平滑地鏈接起來——這個場景下,咱們可以推導出一種能支持兩個控制點的曲線:三次貝塞爾曲線。那麼,難道咱們還須要四次、五次……的曲線嗎?沒必要了!平面字體中各類精巧的形狀,總能夠用若干段最高三次的貝塞爾曲線擬合獲得——咱們能把很長的一段任意曲線近似成許多小段的貝塞爾曲線,它的性質能保證每兩小段曲線在鏈接處的切線斜率都一致,進而讓整條曲線都保持平滑。這種經過近似來化整爲零的工程思路,是否是和浮點數「存在舍入偏差但足夠精確」的原理有些接近呢?字體
上面對貝塞爾曲線概念的介紹中,包含了一個重要的細節:並不是全部的點都在曲線上。繪製曲線時,用到的點其實包含了 on-curve 和 off-curve 兩種。有了這些點的配合,咱們就能組合出複雜的形狀了:3d
上圖中使用貝塞爾曲線繪製這個字母 C 的時候,on-curve 的點是實心的,而 off-curve 的點則是空心的。像這樣的閉合曲線,咱們稱之爲一個輪廓(Contour)。但許多字體的字形可不是一個輪廓就能描述的,像這個:code
這個字母 B 就包含了三個形狀,每一個形狀都對應一個輪廓。TTF 字體的實際應用中,每一個字形能夠有零到多個輪廓。什麼,你說哪有文字的輪廓數量是零?有一種威力強大的字符,叫作空格 :)
在 TTF 標準中,有專門的二進制字段來指定一個字形具有多少個輪廓。頗有趣的一點是,這個數值能夠小於零。輪廓數量是負數又是什麼狀況呢?這就是所謂的複合字形了。好比拉丁語系中各類帶注音符號的文字,其字形實際上能夠由其它的字形組合而成,從而減小數據的冗餘。這大體對應於下面這樣的情形:
這就是曲線輪廓(contour)與字形(glyph)之間的基本關係了。那麼,輪廓上各點的數據又用怎樣的方式定位的呢?這就要引出座標系的概念了。
在 TTF 中,每一個字形都是在獨立的主網格(Master Grid)上給其中的點定位的。不過,網格的座標軸不是無限的,每一個點的 x y 座標只能取 -16384 到 +16384 之間的整數(大大超出了你的屏幕分辨率了吧)。對應的這一塊方形平面長寬約定爲 1em,而每一個網格對應一個 font unit。控制點定位在網格上,勾勒出曲線,像這樣:
點必須在網格上指定,而這個網格的取值範圍則是可變的:網格大小能夠是 2 的整數次冪,這樣一來網格放大一級,就能減小每一個點所佔用的一位存儲空間了。結合 font unit 與 1em 的關係,咱們就能獲得 units per em (UPM) 的概念,這個值越大則字體越精細。好比咱們上一期做爲示例使用的 LiberationSans 字體,就使用了 2048 的 UPM 值。
網格一樣有本身的座標原點。但這時,座標原點的選取可不是隨便在字形上找個角落,而和文字的排版方式有關係。好比對於羅馬字體,x 軸選取在基線(baseline)位置,而 y 軸既能夠選擇放置在字形的視覺中心,也能夠選擇放在左側,與字形帶了少許留白的 left side bearing 重合,直觀地看大概是這樣:
對於漢字,一個頗有趣的地方在於基線位置的變更——記得小時候的草稿紙嗎?練英語的稿紙會印上四條線,其中加粗的第三條就是基線。而中文的基線則放置在文字底部,相似於日記本的橫線上,像這樣:
不過,不一樣字體座標軸的選取也並不算是特別重要,相信你們更感興趣的至少應該是下面這兩件事:
對於前者,在理解了這篇文章的基礎上,有一個很好的方式可讓你直觀地實現簡易的「字體光柵化器」:只要讀取出點的座標,使用現成可控的渲染工具(如 Web 上的 canvas 或 SVG)把這些 TTF 標準下的座標轉換爲路徑,不就曲線救國地實現了文字的光柵化嗎?在不考慮 hinting 等進階狀況的前提下,這個操做並不算複雜。這部份內容仍是蠻有趣的,但願能夠有機會往下寫出來和你們分享 XD
本文的主要內容來源是 Apple 的 Digitizing Letterform Designs 文檔,感興趣的同窗若是移步果家與軟家的字體系列,相信能有更多的收穫~