在上一篇〈Berkeley Mono 與 Ioskeley Mono〉中,我們深入比較了 U.S. Graphics Company 的商業字體 Berkeley Mono (TX-02) 與開源社群基於 Iosevka 打造的 Ioskeley Mono,並透過線上互動實驗室對照了兩者在點狀 0、單層 g、平弧括號 () 以及 Normal / SemiCondensed 字寬上的細節表現。
然而,當我把 Ioskeley Mono 換成日常主力字體、在編輯器與 terminal 裡寫了一整天程式碼後,心裡突然冒出一個小小的念頭:如果我想讓數字 7 的斜桿正中央多加一道水平橫槓(crossbar),就像歐洲手寫體或老式工程圖紙上的 7 那樣,有沒有辦法自己動手改?
如果今天面對的是一般用 FontForge 或 Glyphs 手工繪製的傳統字體,這個念頭大概會在五分鐘內被打消——因為一個完整的現代程式字體家族包含 10 種字重(Thin 100 到 Black 900)、2 種字寬(Normal 與 SemiCondensed)以及正體與斜體,光是一個家族就有 40 個 .ttf 字型檔。手動去改 40 個檔案的貝茲曲線,不僅筆畫粗細與斜率難以對齊,還會破壞原有的 TrueType hinting 與連字表。
但 Ioskeley Mono 最迷人的地方就在這裡:它不是一套靜態的向量圖檔,而是一座由程式碼驅動的參數化造字工廠。 昨晚我在 herdr 的 ioskeley-mono workspace 裡,直接從零複製儲存庫、修改一行設定、一路追蹤到底層的 Patel (.ptl) 幾何語言,並寫腳本用 8 個平行 worker 打上完整的 Nerd Font 圖示補丁。這篇文章就來完整紀錄這趟從原始碼編譯與客製化字體的實戰過程,以及途中踩到的兩個精彩陷阱。
為什麼從原始碼編譯字體比想像中更有趣?
打開 ahatem/IoskeleyMono 的儲存庫會發現,它本身並不儲存成千上萬個字元的曲線座標,核心只有一份不到 400 行的 private-build-plans.toml 藍圖檔,搭配上游的 Iosevka 造字引擎(固定於 v34.4.0)。
當我們從原始碼建置時,整個流程其實是一條橫跨編譯器、計算幾何與二進位封裝的八階段管線:
藍圖結構:四個繼承自同一核心的字體家族
在 private-build-plans.toml 中,作者透過 Iosevka 的繼承機制(inherits = "buildPlans.IoskeleyMono")定義了四個編譯目標(build plans):
- 標準版
IoskeleyMono:設定spacing = "normal"、serifs = "sans"與noCvSs = true,包含完整的程式碼連字規則(calt)、10 種字重、2 種字寬(Normal = 600、SemiCondensed = 540)與11.8°斜體。 - 終端機版
IoskeleyMonoTerm:繼承自標準版,唯一關鍵差異是設定spacing = "term"。這會強制將所有箭頭、幾何方塊與 box-drawing 符號嚴格限制在單一 terminal 字元格(character cell)寬度內,避免在 Ghostty、Kitty 或 Alacritty 中破格。 - 無連字版
IoskeleyMonoNL:設定noLigation = true,供無法單獨關閉連字的編輯器(如舊版 Xcode)使用。 - 網頁子集版
IoskeleyMonoWeb:透過[buildPlans.IoskeleyMonoWeb.subset.include]僅保留拉丁字母、標點、箭頭、數學運算子與框線符號的 Unicode 區間,將原本完整版每個約 480–550 KB 的.woff2透過字元集子集化(subsetting)大幅縮減至約 94 KB。
此外,所有家族都共享一組精心調校的度量參數(private-build-plans.toml 第 201 至 223 行):
[buildPlans.IoskeleyMono.metricOverride]
xHeight = 520 # 小寫字母高度
cap = 690 # 大寫字母與數字高度 (CAP)
ascender = 740 # 上伸部高度
sb = 85 # 左右邊距 (Sidebearing)
accentWidth = 182 # 變音符號寬度與間距參數
accentClearance = 76
accentHeight = 162
accentStackOffset = 208
leading = 1250 # 預設行距
parenSize = 860 # 括號垂直跨度
dotSize = 'blend(weight, [100, 110], [400, 125], [900, 150])'
periodSize = 140
essRatio = 1.03 # 微調 S / s 與問號中央轉折處的水平加寬比例
essRatioUpper = 1.03
essRatioLower = 1.03
essRatioQuestion = 1.03
archDepth = 152
smallArchDepth = 157注意看 dotSize 那行:blend(weight, [100, 110], [400, 125], [900, 150]) 是一個隨字重動態插值的函數,確保在極細體(Thin 100)時標點與 i 的方點不會太小而消失,而在極粗體(Black 900)時又不會過度膨脹糊成一團。
本地端建置環境準備
要在 Linux 本地端從頭編譯 Ioskeley Mono,只需要準備 Node.js 20+(上游 package.json 最低需求為 >=16.0.0,官方 CI 採用 Node.js 20)與 ttfautohint(用於產生標準解析度螢幕所需的 TrueType hinting 指令):
# 1. 複製 Ioskeley Mono 儲存庫
git clone [email protected]:ahatem/IoskeleyMono.git ioskeley-mono
cd ioskeley-mono
# 2. 複製上游 Iosevka 引擎(鎖定 v34.4.0 確保變體名稱與度量完全一致)
git clone --depth 1 --branch v34.4.0 https://github.com/be5invis/Iosevka.git iosevka-src
# 3. 確認系統已安裝 ttfautohint 與 fontforge(後續打 Nerd Font 補丁需要)
sudo pacman -S --needed ttfautohint fontforge python-fonttools為什麼一定要鎖定 Iosevka v34.4.0?因為上游 Iosevka 的 main 分支演進速度極快,有時會重新命名部分字形變體或調整內部幾何常數;固定在 v34.4.0 可以確保我們編譯出來的結果與官方發行版完全一致。
一行設定的魔法:為數字 7 加上水平橫槓
現在回到我們最初的目標:把數字 7 改成中間帶有水平橫槓的版本。
打開 private-build-plans.toml 的第 15 行,在 [buildPlans.IoskeleyMono.variants.design] 區塊中可以看到上游預設使用的是無襯線直桿 7:
[buildPlans.IoskeleyMono.variants.design]
one = "no-base"
two = "straight-neck-serifless"
three = "flat-top-serifless"
four = "semi-open-serifless"
five = "oblique-flat-serifless"
six = "open-contour"
seven = "straight-serifless" # <-- 上游預設:無橫槓的俐落直桿 7
eight = "two-circles"
nine = "open-contour"
zero = "dotted"在 Iosevka 的命名系統中,數字 7 的變體名稱是由三個正交維度組合而成的:[主幹形狀 body]-[左上角襯線 serif]-[中段橫槓 overlay]:
變體名稱 (seven = ...) |
主幹形狀 (body) |
左上角垂直短襯線 (serif) |
中段水平橫槓 (crossbar) |
視覺風格建議 |
|---|---|---|---|---|
straight-serifless(預設) |
直線斜桿 | 無 | 無 | 極簡現代,貼近 TX-02 原生設定 |
straight-serifless-crossbar |
直線斜桿 | 無 | 有 | 最推薦:保留幾何直桿,同時強化 7 與 1/T 的辨識度 |
straight-serifed-crossbar |
直線斜桿 | 有 | 有 | 帶有濃厚打字機與古典印刷感 |
bend-serifless-crossbar |
微彎弧形斜桿 | 無 | 有 | 偏柔和手寫風格,較不適合方正的 TX-02 家族 |
因為 IoskeleyMonoTerm、IoskeleyMonoNL 與 IoskeleyMonoWeb 全部都透過 inherits = "buildPlans.IoskeleyMono" 繼承字形設定,我們只需要改動 private-build-plans.toml 的第 15 行:
seven = "straight-serifless-crossbar"接著把修改後的設定檔複製進 iosevka-src/,啟動 Verda 建置系統:
cp private-build-plans.toml iosevka-src/
cd iosevka-src
npm install
# 同時編譯標準版 (IDE 用) 與 Term 版 (終端機用)
npm run build -- contents::IoskeleyMono contents::IoskeleyMonoTermVerda 是 Iosevka 作者 Belleve Invis 專門開發的增量平行建置引擎(定義於 iosevka-src/verdafile.mjs)。當我們修改 private-build-plans.toml 並針對單一家族執行建置時,Verda 會自動解析相依圖、調動多核心 CPU 平行更新 614 個建置目標(終端機印出 Finished with 614 targets updated.),短短幾分鐘內就在 iosevka-src/dist/ 下產出全部 40 種樣式的 TTF/(Hinted)、TTF-Unhinted/、WOFF2/ 與 WOFF2-Unhinted/。
深入造字引擎:Patel (.ptl) 如何將一行字串編譯成 TrueType 輪廓?
改一行 TOML 就能讓 40 個字型檔同步長出比例完美的水平橫槓,這背後到底發生了什麼事?我在 ioskeley-mono workspace 中一路追進了 iosevka-src/packages/font-glyphs/src/number/7.ptl,發現 Iosevka 內部的幾何編譯管線設計得極為精妙。
讓我們結合實際從編譯後的 IoskeleyMono-Regular.ttf 抽取出來的 TrueType 頂點座標,一步步拆解 "straight-serifless-crossbar" 是如何變成字型檔裡的二進位曲線(下圖與後文的頂點追蹤圖皆直接擷取自編譯後 TTF 的真實 glyf 輪廓座標):
Patel 語法編譯與 SuffixCfg.weave 變體編織
Iosevka 所有的字形都不是手畫的,而是用一種名為 Patel(.ptl)的領域特定語言(Lisp 方言,採用縮排語法)撰寫的程式。由於 Node.js 無法直接執行 .ptl,建置系統第一步會呼叫 patel-c 編譯器,將 7.ptl 轉譯為標準的 JavaScript ES Module(lib/number/7.mjs):
patel-c --strict --esm packages/font-glyphs/src/number/7.ptl -o packages/font-glyphs/lib/number/7.mjs打開 packages/font-glyphs/src/number/7.ptl 的第 65 至 93 行,可以看到 SuffixCfg.weave 如何動態編織出所有合法的 7 變體組合:
local SevenConfig : SuffixCfg.weave
object # body
straight StraightBody
bend BendBody
curly CurlyBody
object # serif
serifless nothing
serifed StartSerif
function [body] : object # crossbar
"" nothing
crossbar : match body
[Just "straight"] Crossbar
[Just "bend"] BendCrossbar
[Just "curly"] CurlyCrossbar
foreach { suffix { Body Serif Crossbar } } [Object.entries SevenConfig] : do
create-glyph "seven.lnum.\(suffix)" : glyph-proc
include : MarkSet.capital
if Body : include : Body CAP
if Serif : include : Serif CAP
if Crossbar : include : Crossbar CAP
create-glyph "seven.onum.\(suffix)" : glyph-proc
include : OnumMarks.p
include : refer-glyph "seven.lnum.\(suffix)"
include : ShiftDown
select-variant 'seven.lnum' [CodeLnum '7'] (follow -- 'seven')
select-variant 'seven.onum' [CodeOnum '7'] (follow -- 'seven')當我們在 TOML 中指定 seven = "straight-serifless-crossbar" 時:
Body對映到StraightBody幾何程序;Serif對映到nothing(不畫左上角垂直襯線);Crossbar根據body == "straight"自動匹配到Crossbar程序;- 最後
select-variant將產生的seven.lnum.straight-serifless-crossbar綁定到等高數字(Lining Figures)的 Unicode 碼位U+0037('7')(同時也透過seven.onum衍生出供 OpenTypeonum特徵使用的老式高低數字版本)。
參數化座標求值:StraightBody 與 Crossbar
當 Node.js 在編譯某個特定字重與字寬(例如 Regular 400,字寬 600、大寫高度 CAP = 690、筆畫粗細 Stroke = 73、左右邊距 SB = 85、右邊界 RightSB = 600 - 85 = 515)時,它會把這些度量常數代入 7.ptl 第 13 至 20 行與第 44 至 49 行的幾何公式中:
define SevenXLeft : mix SB RightSB 0.28
define [StraightBody top] : union [HBar.t SB RightSB top] : intersection
Rect (top - Stroke) 0 SB RightSB
dispiro
widths.rhs
corner RightSB (top - Stroke)
corner [mix RightSB (SevenXLeft + [HSwToV : 0.75 * Stroke]) 2] [mix (top - Stroke) 0 2]
# ... (第 21–43 行定義 BendBody、CurlyBody 與 StartSerif)
define [Crossbar top] : glyph-proc
local m : mix SevenXLeft (RightSB - [HSwToV HalfStroke]) 0.5
include : HBar.m
m - [HSwToV QuarterStroke] - LongJut * 0.8
m + [HSwToV QuarterStroke] + LongJut * 0.7
mix 0 (top - QuarterStroke) 0.5這幾行 Patel 程式碼藏著字體設計師滿滿的視覺補償細節:
- 頂部橫樑 (
HBar.t SB RightSB top):在X = 85..515、Y = 617..690畫出一道頂部切齊CAP (690)、厚度為Stroke (73)的水平橫樑。 - 斜向主幹 (
dispiro+Rect交集裁切): 如果直接畫一條斜線段,斜線的上下兩端切口會垂直於斜線本身(呈現斜角突出)。因此 Iosevka 故意用mix ... 2把dispiro斜桿往外延伸兩倍長,再用一個Rect (top - Stroke) 0 SB RightSB(即Y = 0..617的矩形遮罩)做布林交集(intersection)!這樣斜桿頂端就會完美貼合在Y = 617(頂部橫樑下緣),底端則在Y = 0基線上切出俐落的水平平頭(X = 180..267)。 - 水平橫槓 (
Crossbar) 的不對稱視覺平衡:- 垂直位置:不是單純取
CAP / 2 = 345,而是mix 0 (top - QuarterStroke) 0.5 = 336(扣除頂部橫樑視覺重量後的幾何中心,落在Y = 299..373)。 - 水平位置 (
m):計算斜桿在半高處的水平中心點m。 - 左右突出的視覺微調:注意橫槓往左延伸了
0.8 * LongJut(到X = 158),往右卻只延伸0.7 * LongJut(到X = 501)!為什麼左邊比右邊多出0.1 * LongJut?因為7的斜桿是從右上往左下傾斜,左上方的字懷空間比右下方開闊;左端稍微多探出一點,在人眼看來才會覺得橫槓「正好位在斜桿正中央」。
- 垂直位置:不是單純取
Spiro 螺旋曲線展開與 2D CSG 布林聯集 (typo-geom)
在傳統字體設計中,曲線是由三次貝茲曲線(cubic Bézier)的控制點手拉出來的;而在 Iosevka 中,曲線是由 Raph Levien(同時也是 Google Fonts Inconsolata 與 Zed GPUI 部分文字渲染的先驅)提出的 Spiro 螺線演算法(Euler spiral)計算而成,確保曲率達到 G2 甚至 G4 連續。
經過 spiro-pen-expand.mjs 將一維骨架向兩側展開成二維筆畫多邊形後,重頭戲交給了 typo-geom 幾何引擎:
- 當我們把頂部橫樑、斜向主幹與水平橫槓透過
union組合在一起時,三個零件在十字交會處會有內部的重疊邊線。 - 如果直接把三個重疊的矩形寫進字型檔,在開啟半透明渲染、外框描邊或低解析度光柵化時,交界處會出現破洞(winding rule 衝突)或墨水過深。
typo-geom會執行嚴謹的 2D 建構實體幾何布林聯集運算(CSG Union),自動找出所有交點(例如斜桿與橫槓相交的(300, 299)、(329, 373)、(417, 373)、(388, 299)四個內角),徹底剔除內部重疊線段,融合成單一封閉的 15 頂點外輪廓!
我們直接用 Python 的 fontTools 把編譯出來的 IoskeleyMono-Regular.ttf 拆開,印出 seven 字形的 SVG 路徑指令,就能親眼驗證這個結果:
M180 0 300 299H158V373H329L427 617H85V690H515V617L417 373H501V299H388L267 0Z順著上圖右半邊標出的 [1] 到 [15] 號頂點走一圈,可以看到 typo-geom 如何把原本三個重疊的幾何零件融合成一條順時針方向(TrueType 外輪廓規範)的一筆畫封閉路徑:從基線左下角 [1] (180, 0) 出發,沿斜桿左緣爬升到橫槓下緣交角 [2] (300, 299),往左折出橫槓左翼 [3]➔[4]➔[5],繼續沿斜桿爬升到頂樑下緣交角 [6] (427, 617),繞過頂部橫樑 [7]➔[8]➔[9]➔[10],再沿斜桿右緣一路折出橫槓右翼 [11]➔[12]➔[13]➔[14] 回到基線右下角 [15] (267, 0) 閉合。整顆字元沒有任何多餘的內部節點,而且當 Verda 編譯 Thin (100)、Bold (700)、Black (900) 或 SemiCondensed (90%) 時,這 15 個頂點都會根據各自的 Stroke 與 Width 參數自動重新計算。
二次貝茲曲線擬合 (Quadify) 與 OpenType 封裝 (ot-builder)
最後兩個底層步驟則是跨格式的幾何轉換:
- 三次轉二次貝茲曲線 (
Quadification): Iosevka 內部的幾何運算全部採用三次樣條(degree-3 cubic curves),但 TrueType(.ttf)的glyf表規範只支援二次貝茲曲線(degree-2 quadratic Bézier,每段曲線只有一個 off-curve 控制點)。packages/geometry/src/quadify.mjs會呼叫TypoGeom.Quadify.auto(arc, 1, 8),將所有曲線自動細分並擬合為誤差小於1個字體設計單位(1/1000 em)的二次貝茲曲線。 - 二進位表格序列化 (
ot-builder) 與ttfautohint: 最後由ot-builder將輪廓點座標寫入glyf與loca表、將 Unicode 映射寫入cmap表、將連字與zero特徵寫入GSUB表,產出TTF-Unhinted/,再交由ttfautohint注入對齊螢幕像素網格的 bytecode 指令產出TTF/。
為自訂字體打上 Nerd Font 補丁:平行加速與兩個實戰陷阱
在 Linux 桌面環境(尤其是搭配 eza、lazygit、neovim、starship 與 herdr 的 terminal 工作流)中,只有純文字的 .ttf 是不夠的——我們還需要把 FontAwesome、Devicons、Octicons、Material Design Icons 與 Powerline 符號打包進去,做成 Nerd Font 版本。
平行化批次修補腳本 (xargs -P 8)
官方的 Nerd Fonts Patcher 是一個由 FontForge Python 介面驅動的腳本。如果用單執行緒 for 迴圈逐一修補 40 個字型檔,每個檔案約需 15 至 20 秒,跑完一個家族就得花上十幾分鐘。
實務上,我們可以直接下載 FontPatcher.zip(下方範例固定於 v3.5.1 確保可重現性),並利用 xargs -n 2 -P 8 啟動 8 個平行 worker 同時修補:
#!/usr/bin/env bash
set -e
# 1. 下載並解壓縮官方 Nerd Fonts Patcher (v3.5.1)
if [ ! -d "nerd-fonts-patcher" ]; then
curl -fLO https://github.com/ryanoasis/nerd-fonts/releases/download/v3.5.1/FontPatcher.zip
unzip -q FontPatcher.zip -d nerd-fonts-patcher
fi
DIST_TERM="iosevka-src/dist/IoskeleyMonoTerm/TTF"
PATCHER="nerd-fonts-patcher/font-patcher"
mkdir -p patched-fonts-term/Normal patched-fonts-term/SemiCondensed
patch_font() {
local font="$1"
local outdir="$2"
fontforge -script "$PATCHER" "$font" \
--complete --careful --mono --outputdir "$outdir" >/dev/null 2>&1
echo "Finished $(basename "$font")"
}
export -f patch_font
export PATCHER
# 2. 建立任務佇列(區分 SemiCondensed 與 Normal)
for font in "$DIST_TERM"/IoskeleyMonoTerm-SemiCondensed*.ttf; do
echo "$font" "patched-fonts-term/SemiCondensed"
done > /tmp/fonts-term-to-patch.txt
for font in "$DIST_TERM"/IoskeleyMonoTerm-*.ttf; do
[[ "$font" == *Condensed* ]] && continue
echo "$font" "patched-fonts-term/Normal"
done >> /tmp/fonts-term-to-patch.txt
# 3. 使用 8 個平行 worker 批次修補 40 個字型檔
cat /tmp/fonts-term-to-patch.txt | xargs -n 2 -P 8 bash -c 'patch_font "$@"' _這裡傳給 font-patcher 的三個參數至關重要:
--complete:注入所有開發者圖示集(FontAwesome、Devicons、Octicons、Codicons、Powerline 等)。--careful:若圖示碼位與 Ioskeley Mono 原生已有的字元重疊,保留原生字元而不覆蓋。--mono:將所有注入的圖示嚴格縮放至單一等寬字元格(1-cell),產出家族名稱帶有Nerd Font Mono後綴的字型,確保在 terminal 裡圖示不會溢位蓋住後面的空白或文字。
FontForge 清除 post.isFixedPitch 等寬旗標的副作用
當 font-patcher 透過 FontForge 重新匯出 .ttf 字型檔時,有一個歷史悠久的經典陷阱:FontForge 有時會將 TrueType post 表中的 isFixedPitch 欄位重設為 0。
一旦 post.isFixedPitch 不等於 1,部分 terminal 模擬器或作業系統的字體選擇器就會誤以為這是一套「比例字體(proportional font)」,進而拒絕在終端機字體清單中顯示它,或是導致字元間距計算異常。
因此在 font-patcher 跑完之後,務必補上一段幾行就能跑完的 Python fontTools 修復腳本,把所有 .ttf 的 isFixedPitch 強制改回 1:
from pathlib import Path
from fontTools.ttLib import TTFont
fixed = 0
for f in sorted(Path("patched-fonts-term").rglob("*.ttf")):
font = TTFont(f)
if "post" in font and font["post"].isFixedPitch != 1:
font["post"].isFixedPitch = 1
font.save(f)
fixed += 1
print(f"Restored isFixedPitch on {fixed} fonts")為何重刷 fc-cache 後終端機裡的 7 依然沒有橫槓?
這是我昨晚在 ioskeley-mono workspace 裡遇到最有趣的一個實戰插曲。
當我改完 seven = "straight-serifless-crossbar"、編譯完 contents::IoskeleyMono、跑完 40 個檔案的 Nerd Font 修補、複製進 ~/.local/share/fonts/,並執行 fc-cache -fv 重新載入後——我滿懷期待地在 terminal 裡打出 1234567890,結果畫面上的 7 依然是光禿禿的一根直桿,完全沒有水平橫槓!
第一時間我還以為是 terminal 沒重讀字體快取(在 Kitty 可以按 Ctrl + Shift + F5 熱重載,Ghostty 可按 Ctrl + Shift + , 重載設定,Alacritty 則會自動監聽設定檔變更,VS Code 可執行 Developer: Reload Window)。但就算重開整個 terminal 視窗,7 還是沒變。
一查之下才揪出真正的兇手:
- IDE 與 Terminal 使用不同的編譯目標 (
IoskeleyMonovs.IoskeleyMonoTerm): 檢查我的~/.config/ghostty/config、~/.config/kitty/kitty.conf、~/.config/alacritty/alacritty.toml、~/.config/foot/foot.ini與~/.config/fontconfig/fonts.conf後發現,為了讓 terminal 的格線完美對齊,我所有的終端機設定檔指定的都是IoskeleyMonoTerm Nerd Font Mono(也就是Term家族),而不是標準版的IoskeleyMono Nerd Font Mono。 ~/.local/share/fonts/裡殘留了舊版的Term字型檔: 因為我第一輪只編譯並安裝了標準版contents::IoskeleyMono,而~/.local/share/fonts/IoskeleyMono/資料夾底下還躺著先前安裝的舊版IoskeleyMonoTermNerdFontMono-*.ttf!於是當 Fontconfig 解析IoskeleyMonoTerm Nerd Font Mono時,毫無懸念地繼續指向那批還沒加上橫槓的舊檔案。
找出原因後,解法就非常俐落了:
- 補跑
npm run build -- contents::IoskeleyMonoTerm編譯帶橫槓7的Term家族。 - 執行
patch-term-fonts.sh將 40 個IoskeleyMonoTerm字型檔打上 Nerd Font 補丁並修復isFixedPitch = 1。 - 清理
~/.local/share/fonts/下舊的散落檔案,統一安裝至~/.local/share/fonts/IoskeleyMono/與~/.local/share/fonts/IoskeleyMonoNerdFont/。 - 重刷快取並用
fc-match驗證最終解析路徑:
# 重新建立 Fontconfig 快取
fc-cache -fv
# 驗證 Fontconfig 實際匹配到的字型檔路徑
fc-match -v "IoskeleyMonoTerm Nerd Font Mono" | grep -E "file:|family:"當看到輸出明確指向剛出爐的 ~/.local/share/fonts/IoskeleyMonoNerdFont/IoskeleyMonoTermNerdFontMono-Regular.ttf,再重新載入終端機,整個畫面裡的 7 瞬間長出了那道俐落的水平橫槓!
結語:把字體當作可編譯的基礎設施
從單純「下載別人打包好的 .zip 字型檔」,跨出一步到「自己擁有字體的建置管線」,帶來的掌控感非常奇妙。
透過 Iosevka 的 Patel 參數化引擎與 private-build-plans.toml,字體不再是一顆無法修改的黑盒子:不管你是想幫數字 7 加上水平橫槓、把預設的點狀 0 換成斜線 0、微調行距 leading,還是針對自己的螢幕 DPI 調整字重曲線,都只需要改幾行設定檔,就能在幾分鐘內編譯出一套橫跨 10 種字重、2 種字寬、涵蓋 IDE 與 terminal Nerd Font 的專屬字體家族。如果你也在使用 Ioskeley Mono,強烈推薦把儲存庫 clone 下來親手編譯一次,打造真正百分百貼合自己視覺習慣的開發者字體!