字體切片
中日韓的字體過大,不適合放在網站上,這應該是所有人都理解的事情。但解決方法是什麼?靜態網頁很好解決,在 build 的時候,判斷有哪些文字有被使用到,讓字體只包含這些文字就好了。那動態網頁呢?過去些作法類似於靜態網頁,在網頁載入時動態產生字體檔案。但最近偶然發現 Google Fonts 不是這樣做,覺得很有趣,做個研究紀錄。
字體 Subset
一個字體檔案內,被區分成很多 Table。有的紀錄每個文字資料存放的位置,有的紀錄文字的形狀,有些則是存針對小螢幕裝置優化的規則。Subset 的概念就是讀取字體檔案,依據需要的文字,從各個 Table 找出所需資料,重寫這些 Table,最後輸出一個比較小的字體檔案。目前有很多工具可以協助我們做到這些事情,例如 fonttools。大部分靜態網站生成工具也都可以找到相關的 Plugin。
動態 Subset
一般 Subset 工具都需要預先知道我們需要哪些字體,這對於動態網站來說有點麻煩。如果說要在內容發布時產生字體檔案,那又很難做出一個一般性的工具讓任何網站都能使用,必須跟內容發布有比較深入的整合。有些人想到可以利用 JavaScipt 來解決這個尷尬的情況。
當 Browser 打開網頁時,JS 會偵測網頁內容,將所有用到的文字丟給負責 Subset 字體的服務。這個作法有些麻煩的點:
- 必須等待網頁載入完成,開始執行 JS 後,才能請後端服務 Subset 並下載字體。
- 對後端的要求較高。開始下載字體的時間已經延遲了,還必須要 Subset 字體。所以後端 Subset 的速度必須要非常快,不然會影響使用者體驗。
- 無法 Preload 字體。
字體切片
Google Fonts 採取完全不同的思路,他們將字體切片成很多小的檔案,利用當時新的 css 特性 unicode-range
來載入有用到的字體檔案。最早,這項技術用於韓文字體,嘗試透過分析出各個文字之間的相關程度,以此去切片字體。他們希望可以找到一個最佳的接片方式,讓下載的資料量最小,但又不會發送太多 HTTP 請求(Google Fonts launches Korean support)。
在發布他們研究韓文字體成果的幾個月後,Google Fonts 發布了另一篇文章,介紹了他們研究日文字體後的一些新成果。在研究日文字體的過程中,他們發現日韓字體常用的字其實不多,所以他們改以文字出現的頻率為依據來將字體切片。另一個重要的發現是,支援 unicode-range
等新技術的 Browser 同時也支援 HTTP/2,HTTP/2 可以並行傳送多個小檔案,不會像以前需要考慮請求的數量影響網頁載入效能。所以他們可以不用擔心切片數量,將優化的焦點集中在如何減少傳輸的資料量(Google Fonts launches Japanese support)。
這種作法幾個好處:
- 跨網站 Cache。使用者瀏覽了某個 Google Fonts 的網站後,他的 Browser 會 Cache 住字體檔。當同個使用者在造訪另一個同樣使用 Google Fonts 的網站時,他不需要再次下載檔案。
- 利用 Browser 特性,不需要額外的 JS 才能運作。不需等待 JS 執行。
- 如果不是使用 Google Fonts 等服務,我們可以 Preload 常用的字體切片。(但這樣就失去了 1 的優勢)
Google Fonts 的問題:
- 2022 年,有人因使用 Google Fonts 被罰,因為違反 GDPR。法院建議自己 host 字體檔案。(Leak of IP Address Via Google Fonts Prompts GDPR Fine)
- 無法使用自己的字體。
對任何字體做切片
有時候,我們可能會想要使用一些 Google Fonts 或其他服務沒有的字體,這時候就必須想辦法自己處理 Web font。因為前面提到的一些問題,我們不太可能採用動態 Subset 字體的方式,處理起來太麻煩,速度也不快。比較簡單的做法是,利用 Google Fonts 的成果來切片字體,這樣可以達到跟 Google Fonts 類似的效果。
那我們要怎麼得到文字的使用頻率呢?其實我們不需要知道,直接參考 Google Fonts 提供的 CSS 檔案,我們就可已知道 Google 對各語言字體的切片方式。麻煩的事情 Google 處理掉了,我們只需要負責切片、產生 CSS 檔案就好了。
細節直接看程式碼比較清楚:pycxx/fontina
補充
- Subsetting Web Fonts:Monotype 也採用類似 Google Fonts 的作法
- unicode-range