Mxhlix

我維護一大套「先預覽、再下載成 PNG」的網頁工具 —— 名牌、證書產生器、價目卡、徽章 —— 支援五種語言:日文、英文、西班牙文、法文、葡萄牙文。

只要只測試日文與英文,Canvas 2D 文字渲染看起來都沒問題。

一旦把 es/fr/pt 跑進去,就會出錯。

我踩過這些雷之後,發現所有問題都歸納成四種模式。

前提:拉丁語系語言長度是日文的 1.4–2 倍

先看設計資料。同一個標籤在五種語系下的字數:

範例 ja en es fr pt
工具名稱 22 字 35 62 48 50
「標準」按鈕 4 8 10 20 12

經驗法則:fr/pt 比 ja 長 1.4–1.7 倍;es 甚至可膨脹到近 3 倍。原本為了日文調整的字型大小與 maxWidth,在拉丁語系會放不下。四種失敗模式全部由此而來。

模式 1:手動以 text.split(/\s+/) 換行,在 CJK 上失效

經典程式片段 —— 以空白切分、再逐字換行 —— 對日文或中文完全不起作用,因為這些語言不以空白分詞。一整句變成單一無法斷行的 token,直接在畫布邊緣被裁掉。

請用真實日文輸入測試,並確認最後一行能完整顯示到最後一個字元。「大部分都顯示出來」不算通過。

模式 2:僅支援 ASCII 的斷詞器,會在重音字元處切斷單字

如果用字元類別斷詞器(如 [A-Za-z0-9'\-_])修正模式 1,卻會製造另一個問題:ç é ã ó ñ 不在這個類別裡,因此 produção 會被切成 produ / ç / ão,斷在單字中間。

純英文測試永遠抓不到這個問題。

請用 fr/es/pt 的真實樣本產生 PNG,目視檢查重音字元附近的區域。我找不到其他偵測方法 —— 純字串比對的測試看不出渲染層級的斷行。

模式 3:重要的結尾單字消失在「…」裡

由於 fr/pt 比原本針對 ja 調整的版面長 1.4–1.7 倍,文字超出兩行而被省略。殘酷的是:被省略的通常是片語尾端的字 —— 往往是語意上關鍵的詞,例如 production / produção

解法是把欄位分成兩類:允許截斷的欄位(描述)走「換行+省略」;絕對不能截斷的欄位(標題、姓名)走自動縮放:在離屏 canvas 用 measureText 測量,找出能放得下的字型大小,再以此大小渲染。標題出現省略號就等於交付物壞掉,因此兩種策略絕不能混用。

模式 4:預覽與下載的 PNG 在不同位置斷行

最隱晦的一種。若只在 canvas 端實作模式 3 的自動縮放,而 DOM 預覽仍維持固定字型大小,畫面上看到的斷行位置就會與 PNG 不同。從使用者角度來看,工具的核心承諾 ——「下載即所見」—— 就被打破了。

解法:把自動縮放的字型大小決策集中在一處(離屏 canvas 測量),再把結果套用到 canvas 渲染與 DOM 預覽的 style.fontSize。我的通過條件是:預覽與 PNG 的行數與斷行位置在五種語系下都一致。

發行前檢查清單

針對每一個新的圖片輸出工具:

  • [ ] ja:CJK 文字能完整渲染到最後一行的最後一個字元,不被邊緣裁切
  • [ ] fr / es / pt:產生真實 PNG;ç é ã ó ñ 周圍不發生斷詞
  • [ ] fr / pt:沒有省略號吃掉重要的結尾單字
  • [ ] 全部五種語系:預覽(DOM)與下載的 PNG 在斷行位置與行數上一致

只檢查 en 與 ja 就宣稱「多語言支援完成」,是我踩過最大的陷阱。

限制與注意事項

字元數比例是根據我自己的工具集(實用工具名稱、描述、UI 標籤)量測而來。不同內容類型會使比例改變。

在自動化限制方面:模式 2 與 4 完全無法靠靜態分析偵測。產生 PNG 並實際查看,是唯一有效的方法。五種語系 × 產生檢查是真實成本 —— 任何省略,都會在正式環境中浮現。


驗證日期:2026 年 4–5 月。環境:Canvas 2D API / vanilla JS / 實際上線的 ja·en·es·fr·pt 語系。