終於,到了系列文的尾聲~在 demo 最終結果之前,先來複習一下這一個月以來提到的要點們——「字」是怎麼被電腦讀取、並且顯示出來的?
以輸入 A好耶🥹
這四個字為例,在使用者透過鍵盤或是其他終端輸入時:
儲存端
- 將
A好耶🥹
轉成相對應的 Unicode 碼位: U+0041
、U+597D
、U+8036
和 U+1F979
。
- 評估 UTF-8 / UTF-16 / UTF-32 等 Unicode 實現方案,在考量儲存大小和程式處理等優缺點後,決定字符要採用哪一種方法。
- 以最常用的 UTF-16 為例:
-
A
-> U+0041
-> 0x0041
-> 0000 0000 0100 0001
-
好
-> U+597D
-> 0x597D
-> 0101 1001 0111 1101
-
耶
-> U+8036
-> 0x8036
-> 1000 0000 0011 0110
-
🥹
-> U+1F979
-> 0xD83E & 0xDD79
(代理對) -> 1101 1000 0011 1110 1101 1101 0111 1001
- 將需要的字符按照轉換方案一一轉成二進位,方便儲存。
顯示端
- 渲染引擎從記憶體中讀取要顯示字串的二進位,並按照原本的 UTF 轉換方案反向解碼,獲得原本的碼位資訊。
- 從字型檔案中找到該碼位對應的字符形狀(shape)與外框(outline)資訊,目前大多為向量格式的曲線,但也有可能是點陣圖的樣子,像是一些 Pixel Font、或是 SBIX 格式的 Color Font——像是 Apple 裝置上的 Emoji。
- 以向量圖形來說,又分成二次貝茲曲線(Quadratic Bézier curves)的 TrueType 格式(
*.ttf
)、以及三次貝茲曲線(Cubic Bézier curves)的 OpenType 格式(*.otf
)。
- 從數學的角度來看,高階貝茲曲線的精確性和曲率的平滑程度會比低階的來得好,但過於高階的貝茲曲線在設計時會難以想像把手(handle)和線段的關係。
- 因此,對人類而言,使用三次貝茲曲線的 OpenType 是一個在精確和好操作之間的最佳平衡。
- 從字型檔案中讀取全部字型適用的全域度量(global metric)、以及單個字符獨有的區域度量(local metric)。
- 更進一步,讀取 OpenType 的 Feature 們,處理複雜的文本規格。
- 如果多種文字並存的話,必須考慮複雜文字排版(Complex text layout, CTL)的情況。
- 舉例來説,並不是所有的文字都是 由左至右水平書寫且由上往下換行 (Left to Right / LTR, Horizontal) 書寫的,像是伊斯蘭世界的阿拉伯文與以色列使用的希伯來文,就是 由右至左水平書寫且由上往下換行 (Right to Left / RTL, Horizontal) 書寫。如果這些語言需要和英文並列的話,也就是雙向文本(Bidirectionality),該怎麼處理?
-
- 另外,有些語言是 僅有直排 的,這邊又可以分 由上至下垂直書寫且由左至右換行 (LTR, Vertical, Top to Bottom / TTB),如滿文;或是 由上至下垂直書寫且由右至左換行(RTL, Vertical Top to Bottom / TTB),如傳統的漢字與日文假名。這類的文字如果要和水平的文字放在一起,要怎麼處理呢?
-
- 更令人意外的是,其實這世界上還存在 由下往上垂直書寫(Bottom to Top / BTT) 的文字,還可以細分成 LTR-BTT,像是菲律賓地區塔格巴努瓦語使用的塔格巴努瓦字母(Tagbanwa)、以及 RTL-BTT,像是用來記錄柏柏爾語(Berber)的提非納字母(Tifinagh)呢...一想到這些語言要怎麼處理,就很令人頭痛。
- 最後則是將字符柵格化(rasterization)的過程,從平滑的曲線轉換成點陣形式,以顯示在螢幕上。其中,曲線該怎麼被處理成格子,這又會牽扯到渲染引擎的反鋸齒以及 OpenType 的 Hinting 的技術。