我們平時用於撰寫程式的整合開發環境 (IDE) 或文字編輯器上, 基本上都有一個設定: 設定一個水平定位字元 (HT, ASCII code 0x09) 相當於幾個空白字元 (SP, ASCII code 0x20), 讓我們可以依自己的需求 (或喜好) 調整程式碼的顯示.

但是長久以來 browser 一直都沒有這樣的設定. 而且預設狀況下, HT 字元和 SP 字元的寬度是一樣的, 連續的 HT/SP 字元在顯示上也都會縮減成一個空白字元的寬度. 除非, 它們是落在 <pre> tag 或者 <textarea> tag 裡(註一). 而且 HT 的寬度是固定的 8 個 SP, 沒有辦法更改. 這對教學網站或者分享程式片斷的部落格偶而會造成一點點小麻煩.

後來, CSS3 草案上定義了 tab-size 設定項來解決此問題. 大部份 browser 都是在 2020 年才開始支援此一功(註二) (雖然, 至今 CSS3 都尚未完全定稿). tab-size 的設定主要分為二種格式:

  1. 有附帶長度單位的: 這種很是簡單清楚, 設定值是多少, 顯示上 HT 字元就會被替換成多大寬度的空白.
  2. 沒有長度單位的: 這種則是設定一個 HT 字元代表多少個 SP 字元.

近來我發現, 第二種設定在現今流行的 Google Chrome 瀏覽器 (也包含所有以 V8 引擎實作的瀏覽器) 上有個 bug: 它展開的空白寬度裡沒有計入 letter-spacing 的設定值(註三). 所以造成以下的現象: 當 letter-spacing 設定為 normal (或者是 0px) 時, tab-size 設定可以正確表現, 一旦 letter-spacing 設定值不是 0 時, 就會表現不正常. 所以, 我們原本縮排對齊好了的程式碼 (IDE 環境或者 UltraEdit, VS Code 等編輯器上), 剪下來貼到部落格的 code block 裡之後, 就會看到結果頁面總會有幾行沒有正確的對齊, 尤其是 HT 字元不是前導的空白時.

註一: 更精確的說是 CSS 設定項 white-space 的設定值為 pre, pre-wrap, break-spaces 的 HTML tag.

註二: 依據 Can I Use (caniuse.com) 的資料顯示 Google Chrome 是最早支援的 (Chrome 49 2015/04), 其次是 Mozilla Firefox (Firefox 53 2017/04) 但必需前綴 '-moz-', 其他的 browser 幾乎都是 2020 年才開始支援此一功能.

註三: 規格書 https://drafts.csswg.org/css-text/#tab-size-property 裡說明 tab-size 只含數值沒有單位時, 其寬度應包含 letter-spacingword-spacing.

影響 tab-size 正確展示的因素


實測的結果顯示影響第二種 tab-size 設定正確展示的因素 (即: 影響 HT 字元定位準確性) 有二個:

  1. CSS 設定項 letter-spacing (字元間距) 的設定值及其使用單位.
  2. 中文字型與英文等寬字型的搭配選擇.

其實主要的問題在於 em 這個單位 (或者 rem). 它原本 (印刷術語) 代表的是英文字母的 M 的寬度. 但由於中文字型裡並沒有字母 M. 因此, 後來它變成了代表一個字型的高度. 也有人懶的分到底是還是了, 直接就說是代表 font-size (字型大小).

但是在 pixnet 痞客邦部落格裡又出現其他問題: 桌面版與行動版結果不一致.

  • 桌面版: 同一般網站/網頁的測試結果.
  • 行動版: tab-size 設定無作用. pixnet 痞客邦部落格行動版在網站輸出時將 HT 字元置換成固定的 8 個 SP.

: 中文字型等於 2 倍英文等寬字型時 OK. 中文字型不等於 2 倍英文等寬字型時, NG. 需要額外的調整

此一問題我們可以暫時將 tab-size 的設定值些微的放大, 放大量公式是:
tab-size-字元數 × letter-spacing.(註三)

例一: 原本 CSS 設定是 letter-spacing:0.05em; tab-size:4 時, 應將 tab-size:4 改為 tab-size:4.4(註三). 此處放大量原為 4 × 0.05em = 0.2em, 轉為字元數為 0.4

例二: 原本 CSS 設定是 letter-spacing:1px; tab-size:8 時, 應將 tab-size:8 改為 tab-size:calc(4em + 8px). 此處用 4em 替代 8 個空白字元的寛度(註三). 將設定值 81px 代入放大量公式得放大量為 8 × 1px = 8px.

註三: 放大量固定是字元數 × letter-spacing. 因此新的 tab-size 設定值應該是單位的數值. 如果你想要將新的 tab-size 設定為字元數, 請將其轉為以 em 單位, 再將其數值乘 2. 因為使用 Ubunto Mono 等寬字時中文字 1em 是 2 個英文字母的寬度.

除了 tab-size 本身的設定值之外, 會引起這個 bug 的還有等寬字型的選擇. 在這一篇貼文個人選用的等寬字型是 Google Fonts 裡的 Ubuntu Mono.

我在 2020 年左右開始使用 Ubuntu Mono 這個等寬字型. 之所以用它是因為經過這幾年使用驗證: 它是中文環境下搭配 VS Code 的最佳字型, 它在 VS Code 環境裡本文中英文混雜時沒有參差不齊的問題, 同時也沒有 tab-size 不對齊的問題. 使用其他英文等寬字型, 在顯示純英文程式時沒有問題, 但是一但是中英文夾雜時 (例如: 編寫 md 檔時), 那 tab-size 的設定就 GG 了.

使用 VS Code 編輯中英文混雜的本文時顯示的文字參差不齊的問題一直困擾我許久. 也因此我一直還是抱著沒有這類問題的 Ultra Studio 不放. 一直到 2020 年左右, 找到了這個字型寬度剛好是中文字型的一半的 Ubuntu Mono. 於是開始在 VS Code 裡搭配使用 Ubuntu Mono 字型, 解決了 VS Code 中英文字型不對齊問題, 才正式由 Ultra Studio 轉到 VS Code (哎, 浪費了我花錢註冊了 Ultra Studio 的永久更新).

效果展示


由於行動版的 pixnet 痞客邦部落格輸出頁面時會將 HT 字元轉換為 8 個 SP 字元, 這一段效果展示我將它轉到 CodePen 上.

以下展示第 3 行前段空白使用 8 個 SP 字元, 第 4 行前段空白使用 2 個 HT 字元. 這二行後面的註解前都加了一個 HT 字元.

//       1         2         3         4
//34567890123456789012345678901234567890
        int a, b, c;    // 註解 |
		int d, e, f;	// note |

對照組修正 CSS 設定為: letter-spacing:0.05em, tab-size:2.2em

//       1         2         3         4
//34567890123456789012345678901234567890
        int a, b, c;    // 註解 |
		int d, e, f;	// note |

對照組修正 CSS 設定為: letter-spacing:0.1em, tab-size:4.8

//       1         2         3         4
//34567890123456789012345678901234567890
        int a, b, c;    // 註解 |
		int d, e, f;	// note |

對照組修正 CSS 設定為: letter-spacing:1pt, tab-size:2em+4pt

//       1         2         3         4
//34567890123456789012345678901234567890
        int a, b, c;    // 註解 |
		int d, e, f;	// note |

See the Pen tab-size vs various mono fonts by Jack Ting (@magicjack) on CodePen.