前言:
哥們的專案中用到了樹莓派Zero,就是那個搭載著約十年前單核晶片的單板計算機。效能制約令人痛苦,幸運的是它也具有GPU,於是使用了OpenGL ES、OpenVG等硬體加速框架。其中為了渲染向量字型,接觸了FreeType字型庫。
讀完FreeType文件之後,發現習以為常的字型裡也有大學問,今天我們們來聊聊字型。
字型是什麼?
正如上圖,同樣的內容,不一樣的視覺表現形式。計算機並不需要字型來完成它的工作:查詢、儲存、編輯修改,但只要是通過螢幕、印表機這些視覺介質把文字呈現給人類看,就必須有字型。
無論是開機BIOS列印的簡陋字元,還是日常使用的微軟雅黑、蘋方等非常漂亮的字型,甚至我們們手寫的帶有個人特色的一系列文字都是字型。這麼算下來,字型就是文字的視覺表現形式。
歐柳顏趙,他們的字型有人模仿能以假亂真,但是計算機字型必須做到一模一樣。網頁縮放文字毫不失真,可截圖放大淨是馬賽克。我們們從原理上找找原因。
講個故事:
還記得小學我最喜歡的雜誌叫做《電腦愛好者》,到現在家裡還有幾十本留了下來,其中有一期介紹了清華大學做的一個網站能生成個人的字型。就是在格子紙按照要求的順序寫漢字英文數字,再拍照上傳,就能獲得字型檔案。正好我爸他是個硬筆書法愛好者,就讓他寫了兩千多個常用字(遠達不到GB2312)上傳生成,最後字型真的做了出來。
儲存有那個字型的電腦早已廢棄不用,而且被我折騰成了Linux,雖然希望渺茫,下次回家還是要去找一找那個字型。
字型的表示原理
一般的字型表示方式有兩種:點陣圖(bitmap)、向量輪廓(vectorial outline)。
點陣圖
搞嵌入式的朋友對點陣圖應該不陌生,在12864LCD螢幕上顯示文字要先在電腦上取模,再把獲得的資料寫到原始檔裡。取模的時候要選擇字型的尺寸,16*16、24*24就代表每個字佔多少個畫素。16*16=256,每個字256個畫素,也就要256個bit來確定每一個畫素上是空白(0)還是有顏色(1)。用點陣圖來描述字型,其實就是對畫面的直接表述,用的時候方便快速,直接輸出到顯示即可。
如圖,這樣的文字很有顆粒感,當然也可以製作解析度超高的點陣圖字型,只不過佔用空間就有點大了。除此以外,字號是基本固定的,16*16的字型在12864上大小正常,放到4K螢幕就會非常小,即便畫素4合1合成了更大畫素,顆粒感依然非常強。要想多種字號,就要儲存每個字號的字型,所以目前點陣圖在PC手機上應用較少。
向量
向量字型要求在渲染的過程中進行數學運算才能得出字型。但是隨著算力提升,計算機、智慧手機一般都是向量字型,Windows下的TrueType就是最常見的,結尾為ttf的檔案就是TrueType向量字型檔案。
具體來說,向量表述也用到了點,但這些點並不用來顯示,而是作為向量描述的一部分。連續兩點之間使用數學方法描述出字型的輪廓(如下圖),一段段輪廓首尾相接形成封閉字型。
字型描述中,輪廓是一段段直線曲線組成,,直的叫“線段”(line segment),曲線則是由貝塞爾曲線(Bézier arc),每條線的兩端成為端點(on point,圖中黑色實心),直線段是由兩個on point描述的。
至於貝塞爾曲線,除了端點還需要一個或多個控制點(off point,圖中空心點),一個控制點兩個端點組成二次貝塞爾曲線,如果再加一個控制點就形成三次貝塞爾曲線。貝塞爾曲線在設計領域的廣泛應用也是因為如此優雅簡單的描述方式。
不過,當遇到複雜的字元,比如某些漢字,描述會異常複雜,也就需要大量控制點和端點。而且,這些點也要放在劃分好的點位上,不能隨意放置。在設計字型時,往往會先設定一個方形區域,稱為EM square。這個EM區域會被劃分成許多的單元(unit),單元的數量越多,可以認為設計字型時可選的點位也越多,字型也就可以更精細。一般來說TrueType字型會有2048個unit。
對比一下
點陣圖,對字型影像的直接描述,不適合縮放。由於不需要縮放演算,用起來比較快。
向量,對字型的輪廓描述,放縮無失真,有算力要求。
字元排版要素
應用廣泛的向量輪廓表述在實際應用中會遇到不少問題,比如螢幕是由畫素組成,向量輪廓計算再精確也逃不過最後要顯示到分立的畫素上去。再者,漢字較為方正,換做其他文字可能會出現需要處理的地方。看完FreeType文件後,不由感謝相關工作者的付出,平常被忽略的細節裡隱藏著無數的工作成果。
以上圖中的example
為例,字母p
非常的瀟灑,甚至都延申到了a
。文字是斜的,完全分不出字間距,amp
三個字連在了一起。這已經非常像人類用手書寫的文字了,但問題是,計算機是怎麼做到的。
下面是計算機渲染文字時常用的一部分步驟。
字距微調(Kerning)
讓我們簡化一下情況,討論比較常見的字元。
按照常見的從左到右書寫方式,字元會一個挨著一個地排列,如下圖:
圖中帶箭頭線段的長度被稱為進寬度
(advance width),它們的寬度各不相同。在同一行上,下一個字型開始的位置要根據前面文字的advance width來確定,但很多時候並不是單純的相加,比如BRAVO:
對比兩個BRAVO,發現即使文章裡的A與V都是第二種情況,在非斜體的情況下豎直方向上沒有留出通路。這種樣式更符合書寫和閱讀習慣。這樣的調整被成為字距微調(kerning)。其實現原理基本就是預置一些規則表比如kern和GPOS表,當連續文字出現這些組合的時候,就按照設定調整字距。其中GPOS還能根據上下文內容來進一步優化,比如當前字元在句子中的位置。
放縮中的柵格對齊(grid-fitting,hinting)
向量字型在渲染前必須放縮到需要的大小,再經過計算轉換成畫素描述的點陣圖以便顯示。按照之前說的向量輪廓的組成方法:端點與控制點,把所有的點位置等比放大進行計算,就是需要的結果。所以,這就要求各個字元放大的中心點要一致,不然放大後字元參差不齊。
在轉換成點陣圖時,因為解析度達不到無窮大,就會出現一些問題。比如H
字母應該是對稱的,但如果非常不巧,一些控制點或者端點在縮放後落不到顯示器的畫素柵格上,就可能會出現H兩邊粗細不同,E的三橫長短不一這種情況。柵格對齊(grid-fitting或hinting),就會稍微變化一下字型比例,將所有的點放置到畫素柵格上去,保證渲染顯示不出問題。
除此以外,柵格對齊還能優化縮放後閱讀觀感,比如小寫的m
在一些低解析度螢幕上如果縮小太多,它下部會變得非常密集影響美觀和辨識度,這時hinting就會把字元橫向拉長一些,下部的間距由此變大,更加美觀。
總結
最近搞得東西資料總是很少,官方英文文件動輒幾百頁,昏天黑地的讀下來可以說是痛並快樂著。
字型與顯示方面知識繁多,不少內容是第一次接觸,覺得值得記錄,本文內容不過是九牛一毛。
帶有括號的名詞是自己起的名字,請以括號內英文為準。
技術新人,水平一般,文章如有錯誤請一定指出,如有其他意見也請不吝賜教。
參考:
FreeType官方文件:https://www.freetype.org/freetype2/docs/glyphs/index.html
OpenVG1.1官方文件:https://www.khronos.org/registry/OpenVG/specs/openvg-1.1.pdf
更多嵌入式相關內容,請來公眾號,找我聊聊天吧:
歡迎轉載,轉載請註明作者與原文連結。
作者:胡小安