← 返回首页

终端 Nerd Font 图标显示成方块的修复:用 Symbols-Only 字体做 Fontconfig 回退

2026-06-02 · LinuxFontconfig终端Powerlevel10k

环境: Ubuntu 26.04、GNOME、Ptyxis 终端,等宽字体 Ubuntu Sans Mono,提示符用 Powerlevel10k(nerdfont-v3 模式)。

终端里 Powerlevel10k 的那些图标——git 分支、powerline 箭头、home 小房子——全显示成方块(tofu / 豆腐块)或空白。常见建议是"装个 Nerd Font 当终端字体",但完整版 Nerd Font 把字母也一起换了,字形偏笨,我并不想丢掉现在这套好看的 Ubuntu Sans Mono。

目标很明确:正文继续用原字体,只把缺的图标补上。本文记录怎么用一个「只含图标、零字母」的回退字体干净地做到,以及中间一个非踩不可的坑。

根因:p10k 在发图标码位,但没字体能画

Powerlevel10k 设成了 POWERLEVEL9K_MODE=nerdfont-v3,它会输出 Nerd Font 的图标码位(大多落在 Unicode 私有区 PUA,如 U+E725 git 分支)。但系统里压根没装任何 Nerd / Symbols 字体:

$ fc-list | grep -iE 'nerd|symbols'
NotoSansSymbols2-Regular.ttf: Noto Sans Symbols2 ...
NotoSansSymbols-Regular.ttf:  Noto Sans Symbols ...
# 只有 Noto 的通用符号字体,不含 powerline / devicons 那些图标码位

发出去的码位没有任何字体覆盖,渲染层只能画成方块。

思路:别换主字体,只挂一个「图标专用」回退字体

Nerd Fonts 官方除了"给某字体打补丁"的完整版,还发布一个 NerdFontsSymbolsOnly —— 里面只有图标字形,不含任何拉丁字母和汉字。把它装上,再用 fontconfig 挂成等宽字体的回退即可:

装字体(只取单字宽的 Mono 版,终端对齐更稳):

mkdir -p ~/.local/share/fonts
cd /tmp
TAG=$(curl -s https://api.github.com/repos/ryanoasis/nerd-fonts/releases/latest \
      | grep -oP '"tag_name":\s*"\K[^"]+')
curl -sL -o SymbolsOnly.zip \
  "https://github.com/ryanoasis/nerd-fonts/releases/download/${TAG}/NerdFontsSymbolsOnly.zip"
unzip -o SymbolsNerdFontMono-Regular.ttf -d ~/.local/share/fonts/ SymbolsOnly.zip
fc-cache -f

$ fc-list | grep -i "symbols nerd"
SymbolsNerdFontMono-Regular.ttf: Symbols Nerd Font Mono:style=Regular

第一版回退规则:append —— 然后翻车

直觉是把回退字体"追加"到等宽字体后面。建 ~/.config/fontconfig/conf.d/76-nerd-symbols-fallback.conf:

<match target="pattern">
  <test name="family"><string>monospace</string></test>
  <edit name="family" mode="append" binding="weak">
    <string>Symbols Nerd Font Mono</string>
  </edit>
</match>

刷新缓存后逐个码位验证,大部分图标都对了,但有一个例外:

$ for cp in E0B0 F015 E725 F09B; do
    printf "U+%s -> %s\n" "$cp" "$(fc-match "monospace:charset=$cp" | sed 's/.*: //')"
  done
U+E0B0 -> "Symbols Nerd Font Mono"   # powerline 箭头 ✓
U+F015 -> "Symbols Nerd Font Mono"   # home ✓
U+E725 -> "AR PL UMing HK"           # git 分支 ✗ 被 CJK 字体抢走了!
U+F09B -> "Symbols Nerd Font Mono"   # github ✓

U+E725(git 分支图标)落到了 uming.ttc(AR PL UMing HK)——一个中文字体。

真正的坑:CJK 字体会"霸占"私有区码位

Nerd Font 的图标码位在 Unicode 私有区(PUA)。而很多 CJK 字体(uming 是典型)也会在 PUA 塞自己的字形。于是同一个 U+E725,两个字体都声称"我有"。append(追加到末尾)让 Symbols 字体排在 CJK 之后,CJK 就赢了——你会看到一个莫名其妙的汉字而不是分支图标。

这和之前那篇中文显示成日文字形是同一类问题:字体在 fallback 链里的排序决定了谁来画这个码位

修复:prepend + strong,让图标字体排到最前

append 改成 prepend(放最前)、weak 改成 strong:

<fontconfig>
  <match target="pattern">
    <test name="family"><string>monospace</string></test>
    <edit name="family" mode="prepend" binding="strong">
      <string>Symbols Nerd Font Mono</string>
    </edit>
  </match>
  <!-- 终端可能直接请求具体字族名,而非 monospace 别名,补一条 -->
  <match target="pattern">
    <test name="family"><string>Ubuntu Sans Mono</string></test>
    <edit name="family" mode="prepend" binding="strong">
      <string>Symbols Nerd Font Mono</string>
    </edit>
  </match>
</fontconfig>

为什么 prepend 到最前也安全? 因为 Symbols Nerd Font Mono 只有图标、没有字母和汉字。遇到字母 ‘a’ 它没有字形,fontconfig 自动跳到下一个字体(Ubuntu Sans Mono);遇到汉字它也没有,跳到 CJK 字体;只有图标码位它才真正接管——从而盖过 uming。

验证:取证而不是凭感觉

刷新缓存,用终端实际请求的字族名 Ubuntu Sans Mono 来核对(注意别只测裸 monospace 别名,那解析出来的首字体未必是终端真正用的):

$ fc-cache -f

# 图标码位 → 全部命中 Symbols Nerd Font
$ for cp in E0B0 F015 E725 F09B F126 EA60; do
    printf "U+%s -> %s\n" "$cp" \
      "$(fc-match "Ubuntu Sans Mono:charset=$cp" | sed 's/.*: //')"
  done
U+E725 -> "Symbols Nerd Font Mono"   # git 分支,修复 ✓

# 正常字符不被符号字体抢走
$ fc-match 'Ubuntu Sans Mono:charset=61'    # 字母 a
"Ubuntu Sans Mono" "Regular"                # ✓ 原字体保留
$ fc-match 'Ubuntu Sans Mono:charset=4f60'  # 汉字 你
"Noto Sans Mono CJK SC" "Regular"           # ✓ 中文不受影响

fontconfig 的改动只对新启动的进程生效——开一个新的终端窗口,Powerlevel10k 的图标就正常了。p10k 本身一行都不用改(它本来就在发正确的码位,只是之前没字体能画)。

小结