Well in Time
透過減少 ttf 字體檔案大小來增進網頁效能
04-23-20218 Min Read

最近很迷 Netflix 上的 Formula 1: Drive to Survive,看各家車廠與車手為了爭奪那幾秒的速度而拼盡全力,非常熱血勵志,把這精神延伸到網頁製作上,大概就是要將網頁效能調整到極致,各種能減少 Loading 時間的技巧都得用上。

前言

最近公司有個行銷活動需要做一個簡單的 SPA,基本上只有簡單的三個頁面,完全可以利用 Gatsby 或 Nextjs 來製作靜態頁面,然後部署到 CDN 上頭,效能上來說理應足夠好了,但是我們的設計師在頁面上混用了多種的字體,尤其是日文部分,除了一般瀏覽器內建的字體外,某些元件採用額外的免費字體,例如 Corporate Logo Font,這代表我們需要額外去下載這些字型,但為了頁面上的幾個字,去下載一整個字型檔案(ttf, 2.6MB)實在很浪費,因此只好來研究一下如何客製化字型檔案,只載入我們需要的字。

雖然這感覺是個很容易遇到的需求,但我還真的是第一次實際需要處理,感謝同事 Carlos 提供解法,透過這篇文章筆記一下,希望對他人也有點幫助。

開始前先稍稍複習一下,什麼是 ttf?還有什麼其他字型格式呢?

TTF(TrueType Font)是由蘋果和微軟共同開發的一種電腦輪廓字型類型標準,是 Mac 與 Windows 上最常見的格式,基本上所有主流瀏覽器都支援,也是免費或便宜的第三方字體最常提供的格式。缺點是檔案未經過壓縮,文件大小較大。

另一個主流格式為 OTF(OpenType Font),是一種可縮放字型(scalable font)電腦字型類型,由 TrueType 延伸而來,採用 PostScript 格式,是微軟與 Adobe 聯合開發,用來替代 TrueType 字型的新字型。

WOFF(Web Open Font Format) 則是完全為了 Web 而設計的格式,由 Mozilla、Microsoft 與 Opera 合作推出。WOFF 的字型都經由 WOFF 的編碼工具壓縮,體積能比 tff 小 40%,現在已經是網頁字體的推薦標準。WOFF2 則是 WOFF 的升級版,體積可以壓得更小。

最後,當然大家熟悉的 SVG 也可以算是一種。

在主流的作業系統與瀏覽器上,這幾種格式的支援度都很高,而其主要的細節差異,因為非本篇重點,就不多著墨,有興趣的讀者可以到 wiki 上查看。

今天要減少的字型檔案為最常見的 tff 格式。

順帶一提,當我在撰寫文章的時候,對於字型、字體等名詞的差異很模糊,好在 JustFont 在多年前的一篇文章解釋得蠻清楚的,推薦大家理解一下!

Setup

要針對字型進行處理的話,首先我們需要下載 FontForge,FontForge 是一個很有名的軟體,可以用來設計、創建字體,或是進行各種字型相關的操作,可以從 https://fontforge.org/en-US/downloads/mac-dl/ 下載 Mac 版本(也有 Linux 與 Windows 的版本)。

在官網上你可以找到許多文件,甚至是一整個 ebook 來教你如何用 FontForge 來設計字體。

用 FontForge 開啟原始字型檔

載好 FontForge 後,我們先打開原始的字型檔,這邊以前面提到的 Corporate Logo Font 為例(註: 在 MacOS Catalina 或 Big Sur,直接點選載好在 Apps 內的 FontForge app 可能會被 OS 擋下來,快一點的方式是按右鍵 -> "Show Package Contents" -> "Contents" -> "MacOS",然後點選 FontForg.app,接者就會打開 terminal 並執行 FontForge。):

original-font-file-in-fontforge

開啟後可以看到所有的字圖,接著其實你就可以選取你不要的字圖,然後 clear 掉它們:

clear-font

但照這樣處理,弄到天荒地老六親不認都弄不完。工程師要用更聰明的解法。

正確的姿勢

打從一開始,我們就是因為原始字檔裡面太多我們不要的東西,我們需要的很少,才想要從原始檔案中擷取需要的部分,既然如此,就應該從我們想要的字圖下手,而不是慢慢刪掉我們不要的字圖。

FontForge 其實有提供一個很方便的功能,叫做 Invert Selection,能夠選取所有你沒有選取到的東西,直接看個動圖範例:

fontforge-invert-select

這樣一來,就很簡單了,只要選取住你想要的字圖,然後點選 Edit -> Select -> Invert Selection,就完成了,接著就把 Fontforge 自動幫你選取的字圖 clear 掉即可。

但這樣還是有個問題。

字型檔內容這麼多,我要手動在 FontForge 中找到自己想要的字圖不也是找到山窮水盡嗎?

正確姿勢二

FontForge 是個蠻強大的工具,除了 GUI 以外,也提供 interpreters,讓你能撰寫 scripts 來修改字型檔。

一個 interpreter 是 Python,另一個則是其內建的 scripting language。詳細的範例、語法等可以從官網查看,文件很完整

有了 scripting 的功能,我們就不用自己手動選取字圖啦。

在 FontForge UI 上,你可以點選 File -> Execute Script 叫出 Dialog,可以選擇直接貼上 Python 程式碼,也可以選擇 FF -> Call,來載入使用另一個內建 interpreter 的 script file。

fontforge-exec-scripts

因為我們要處理的動作很簡單,只有三個動作(選取字型、反轉選取、刪除),所以直接用內建的 script language 其實比較簡單,可以利用 NodeJS 來產生執行檔。

需要的 API

三個動作,選取字圖、反轉選取、刪除,分別對應的 API 為 SelectMore()SelectInvert()DetachAndRemoveGlyphs()

我們要匯入進 FontForge 的執行檔,就只需要這三個 API 即可。

SelectMore() 用法是傳入字型的 unicode 作為參數,即可選取該字圖,不過執行一次只能選取一個字圖。SelectInvert()DetachAndRemoveGlyphs() 則不需要參數。

範例程式

知道了需要的 API,我們就可以來寫程式產生執行檔 subset-font.pe.pe 是 FontForge 可以接受的格式,.ff 也行:

// 挑選出你頁面上需要用到的字
const characters = '123招待コード';

const stream = fs.createWriteStream('./subset-font.pe');

stream.once('open', function (fd) {
  characters.split('').forEach(char => {
    // 轉換成 16 進位
    let hex = char.charCodeAt(0).toString(16);
    // 補零,以符合 \u 格式
    if (hex.length < 4) {
      hex = hex.padStart(4, '0');
    }
    // 然後執行檔內寫入 SelectMore
    stream.write(`SelectMore("u${hex}")\n`);
  });
  // 反轉選擇,選取所有其他不要的字
  stream.write('SelectInvert()\n');
  // 最後移除字型
  stream.write('DetachAndRemoveGlyphs()\n');
  stream.end();
});

利用 FontForge API 搭配上面程式後,會產生以下內容:

SelectMore("u0031")
SelectMore("u0032")
SelectMore("u0033")
SelectMore("u62db")
SelectMore("u5f85")
SelectMore("u30b3")
SelectMore("u30fc")
SelectMore("u30c9")
SelectInvert()
DetachAndRemoveGlyphs()

接著依照正確姿勢二的方式,匯入此執行檔,FontForge 就會產生只包含我們想要的字圖的字型檔了!:

fontforge-script-subset-font.gif

不過因為刪掉的字圖很多,我們可以進一步透過 FontForge 的壓縮功能來輔助我們檢視成品:

剛執行完 script 後,畫面會停留在 select 所有其他你不要的字圖的狀態,你可以先隨便點選空白處 deselect 所有字圖,然後選擇 Encoding -> Compact

fontforge-compact

就能清楚看到整個檔案的確只剩下我們所選的字圖(以上面範例 script 來說就是 123招待コード)。

最後步驟就是產生字型檔案,點選 File -> Generate Fonts...,然後看你要 export 成什麼格式,如果是網頁上要用,當然就推薦使用 woff

fontforge-subset-font-generate-file

按下 Generate 後可能會出現 Error,可以不用理他,繼續 generate:

fontforge-generate-error

這樣就大功告成了!

(註:關掉 FontForge 時,記得選 Don't Save,不然會蓋掉原始的檔案喔!)

結論

這個方式可以用在各種字體檔案,非常方便,對於靜態頁面上內容文字不太會變動的狀況下,利用這個技巧可以大幅降低需要載入的檔案大小,以我公司專案的例子來說,從原本 2.6MB 的 tff 檔案,最後可以變成 8KB 的 woff 檔案,省下的大小很可觀的。

簡單的筆記,希望對大家有幫助!

資料來源

  1. Web 字體簡介: TTF, OTF, WOFF, EOT & SVG
  2. Delete all unused characters from a TTF-font with Fontforge
  3. fontforge docs
  4. [but] 雜談 ─ 常常搞混的一些詞
© by Arvin Huang. All rights reserved.
theme inspired by @mhadaily
Last build: 09-08-2024