fbpx
分類
Computers 學習筆記

東拼西湊個 webfont service

一直以來,敝社網站面臨著有點令人尷尬的情況:為了閱讀上的美觀,網頁版型的標題應該要是襯線體(明體/宋體),但在不同平台上看到的字體卻總是無法有一致的體驗。為此,我們有幾種可能的作法:

  1. 把各種系統有的襯線體都寫進 CSS,讓瀏覽器自己去 fallback
  2. 直接用 CSS 把整個字體讀進來
  3. 使用 typekit 或是 justfont 這類的動態 webfont service

因為辦公室 90% 以上設備都是 Mac 或是 iOS(辦公室只有我用 PC,孤單寂寞覺得冷),好一段時間我們都使用 solution 1,依序以「冬青體、宋體 TC、儷宋體、新細明體」的順序寫進 CSS。但時不時會因為標題出現了冬青體沒有的漢字,fallback 變成了宋體、儷宋甚至是新細明體時的缺字或是 baseline 歪掉了的問題。baseline 的問題或許可以用 CSS 去解,但兩套不同字體的字重,甚或是不同語系的漢字筆順寫法,總是會在無意間就強暴了閱讀者的眼睛(用 Windows 則永遠都是新細明體,反正一直以來都很醜所以..)。

至於 solution 2,我們曾經嘗試過直接讀入 Google Font 提供的、體積較小的 cwTeXMing,但只有 Big5 字集的結果,是顯示時反而更容易出現缺字的情形,而超過 2MB 的大小,初次讀入頁面的時間與流量成本也是非常可觀的。其他支援 Unicode 的 IPA 明朝或是花園明朝的粗體實在不甚好看,體積也十分龐大,算不上是堪用的解法。

於是我們轉向了 solution 3,直接使用別人提供的解決方案確實是很愉快,把字體設定好,套上 style 便可打完收工,但 script 裝上去兩三天,我們就把免費的 quota 吃完了(typekit 是 25K PV/month,justfont 是 10K PV/month)。要嘛就交個保護費,要嘛就退回到 solution 1。因為服務費用貴貴的,所以我們就繼續讓 Windows 使用者眼睛受傷了(錯)。


精美的思源宋體

這個問題就這樣被我放置 play 了三四個月,直到最近思源宋體的推出。思源宋體在各種字重還有不同語系的筆畫寫法上都有著墨,顯示效果也比其他開源字體來得優異許多。在推出當天便可以用 typekit 所提供的 webfont 服務掛上網頁。但問題依舊,按次計算的費用十分可觀。
如果是在自家網頁顯示思源宋體,到底該怎麼辦呢?快速地想了一下,或許可以這樣:

  1. 付保護費
  2. 寫隻小程式,在 server 端把字體 render 成透明背景的圖片掛進頁面
  3. 自幹 webfont service

solution 1 好貴,我付不起(炸)(醜哭)。網頁整頁只用到標題幾個字,跟整頁字體都使用 webfont 的成本是一樣的,怎麼看都覺得很微妙。
solution 2 已經是行之有年的作法,但遇到 RWD 網頁就會爆炸,不同字體大小或是螢幕解析度也會帶來各種大大小小的問題,實在不是很好。至於一個字一張圖或是把字體轉成 SVG 圖片之類的作法因為太硬派了我們還是當作沒看到好了。

那麼,solution 3 呢?之前曾經看過 timdream 小帥提 – 提姆提拉米蘇 在 IE 6 上(對,不要懷疑,就是 IE 6)實作過中文的 EOT font subset,效果不錯,但印象中似乎是固定的文字集。我也忘了當時他是用甚麼工具去把 font subset 拆出來了(補充:小帥提在 comment 中表示是使用 WangShen Lu 大大提供的 script)。

於是就用 font subset 當關鍵字翻找了一下網路,很快地便找到了 Google 的網頁字體最佳化指南,當中提到了 fonttools 這套工具,其中提供了 pyftsubset 這個指令,可以依照傳入的文字生出不同的 font subset。

那麼,這麼好用的工具要怎麼安裝呢?請點進 GitHub 看一下,因為太簡單了所以這裡也不贅述。

於是我很快速地把 fonttools 裝起來,看了一下 pyftsubset 的說明:


pyftsubset –help

看來只要用 --text 或是 --text-file 就能讓程式生出有那幾個字的 font subset 了。於是便開心地下載了思源黑體的 OTF 檔(我們只用 SemiBold 這個字重),然後打了指令測試:


pyftsubset SourceHanSerifTC-SemiBold.otf –text-file=test.txt

嗯,在 R700 這台老筆電上跑一下就跑完了,看起來沒有錯誤,生出來的 subset 大概 12KB,所以趕緊來寫個 HTML 測試測試:


測試用的 HTML

「唔喔喔!真的會動耶!」


IT’S ALIVE!!

寫到這邊,其實會 Python 的朋友們已經可以自己動手用 fonttools 包個 service 出來了。

但資質駑鈍如我,還是比較孰悉 PHP,所以就用了萬能的 system call 去吐產生的字體檔,輸入的 query string 就是需要使用的文字字元。

作法有很多種,我是直接把輸出導到 /dev/stdout,把結果直接吐出來,因為程式碼太醜了所以我就不貼上來了,大致上就是用 shell_exec("pyftsubset 一大串醜醜的東西") 這樣(遠目)

至於效能問題.. 用 system call 真的.. 會.. 比較.. 慢,不過只要 cache 做得好,除了第一次讀入的人比較悲劇之外,後面的 request 應該堪稱迅速。秉持著一貫地懶惰,我也只用了目前線上機器架構中既有的東西拼湊:對外放個 cache server (Varnish Cache),更外面再放個 CDN (Cloudflare, free tier),防止大量 requests 打爆機器。

嗯,server 端的部分真的就只有這樣,真正有作用的程式碼也大概只有兩行,就算寫得再爛都不會太難讀懂(掩面),需要特別注意的是要送出 CORs header,還有記得防止 XSS 還有 arbitrary code execution 之類的攻擊。

接著就是 client 部分啦。

我採取的是在讀入頁面之後,用 DOM selector 把需要套用字體的物件文字撈出來,sort、unique 之後,送回 server 生出相對應的字集字體,再用 jQuery 套上去的 approach,如此作法有幾個好處:

  1. 對於用到同樣字集的網頁,request uri 會長得一樣,cache 會比較簡單
  2. 產生的 request uri 相對短一點(真的就是短一點點)
  3. script 寫一次就可以到處撒(咦)

那麼,該怎麼動態產生 font-face 的部分呢?嗯,我也不知道(好不負責)

因為我很懶惰,所以用了現成的 FontFace jQuery Plugin 來載入字體。

很快速地讀了一下程式碼,依照需求寫出的 client 端程式碼看起來大概是這樣:


總之就是一坨義大利麵

然後只要打個 loadFont('.someClass'); 就可以動態載入字體了,真是非常愉快呢 ww

需要注意的是,如果在網頁裡要 call 很多次,每次的 font name 要不一樣,否則套用到不同物件時只會讀入第一次載入的 font name 的字集。在這裡我使用了 stack overflow 大法找到了很好很強大的解答,測試了一下會動所以就沒有再多做甚麼修改。

於是這東拼西湊,這個速度不是很快 (!) 而且因為用 query string 所以不能傳太多字進去 (!!) 然後還只能吐一種字體 (!!!) 還弱弱第只支援 modern browser 的 webfont service 就跑起來啦 wwww

從此之後,我們的網頁就有了精美的中文襯線體:


精美啊!

至於讀入字體時的 FOUT, FOIT, FOFT 問題,可以看一下這篇文章,裡面有提供幾個 robust 的解法,加上去就可以解決一些惱人的字體閃爍問題喔 :D

雖然自己 host 還是比不上那些 service provider 的速度,但租台小台 VPS 的成本非常低,加上 CDN 後效能其實沒那麼慘,在用量不大的狀況下就湊合著用吧。

以上就是這次的義大利麵料理指南,大家快去找個小廚房(利益揭示:連結是我的 DigitalOcean 推薦碼,申請成功您可以拿到 USD $10 credit,最小台的機器可以用兩個月喔)來料理一下吧 (?)

補充:剛剛 高偉格 大大說可以直接用 font-spider 來建 service,啊啊啊相見恨晚 QQ