字体与数据库工具

「字体明明装了,LaTeX 却找不到」——本页就是为回答这一句而写。麻烦之处在于,不同引擎寻找字体的方式完全不同pdflatex 根本不看系统字体,它只相信 updmap 生成的字体映射;XeTeX 会去问操作系统的字体机制;LuaTeX 查的是 luaotfload 建立的一套自有 Lua 数据库。所以「fc-list 里有,却用不了」并不矛盾,只是你看的是三套机制中的另一套。下文按「各自修复哪一层故障」的顺序,依次讲 fc-listfc-cacheupdmapkanji-config-updmapluaotfload-toolfmtutil

引擎是怎么找到字体的——三套彼此独立的机制

这种三分,正是各引擎诞生年代之差原封不动地保留了下来。pdfTeX 的血统来自 1980 年代的 TeX,那时操作系统里根本没有「字体目录」这个概念。于是 TeX 侧的逻辑名(诸如 ptmr8r 之类的字符串)与实际文件的对应关系,被写进一张叫作字体映射的表,而 updmap 负责把它拼装出来。XeTeX(2004 年起)的设计是直接挂在系统字体机制上:对 macOS 版 TeX Live 2024 的 xetex 执行 otool -L,可以看到链接了 CoreText。LuaTeX 则用 Lua 自行解析字体名,查的是 luaotfload 建立的数据库。同样写下 \setmainfont{...},这一行在三者之下问的是三个不同的权威。

引擎如何寻找字体修复用的命令
pdftex完全不看系统字体;通过字体映射把 .tfm 逻辑名解析为文件updmap-sys
xetex向操作系统查询;macOS 版链接了 CoreText操作系统的字体管理
luatex在 Lua 中解析名称,查阅 luaotfload 建立的名称数据库luaotfload-tool --update
dvipdfmx负责的不是排版而是嵌入:由映射决定哪些字体进入 PDFupdmap-sys / kanji-config-updmap-sys

fc-listfc-cache 并不是 TeX Live 的命令

这一点若弄错,后面的判断就会跟着全错。fc-listfc-cachefc-match 都属于 fontconfig 这个独立项目,并不包含在 TeX Live 中。 把 TeX Live 2024 的 bin 目录从头翻到尾,也找不到任何以 fc- 开头的可执行文件;这台 Mac 上的那几个,是 Homebrew 安装的 fontconfig 2.18.2。它们依然有用,因为能回答系统里有哪些字体、文件在什么位置。fc-list : family 只列出族名,fc-list -f 则按指定格式列出 file、family 与 style。本机上返回了 2,799 条。

terminal
fc-list : family | sort -u | wc -l         # how many families the system offers
fc-list -f '%{file}\n' :family=Menlo        # where the file actually is
fc-cache -fv ~/Library/Fonts               # rebuild the fontconfig cache

fc-match "NoSuchFontXYZ"
# Verdana.ttf: "Verdana" "Regular"          <- a substitute, not an error

这里有个人人都会中一次的坑:fc-match 从不说「找不到」。 就算给它一个并不存在的名字,fontconfig 也总会返回点什么:本机上 fc-match "NoSuchFontXYZ" 返回的是 Verdana.ttf: "Verdana" "Regular"。那是替代,不是确认。想用 fc-match 判断某字体是否已安装,它永远会回答「装了」。真要判断,就把返回的族名与你询问的名字比对,或用 fc-list 指名收窄。macOS 上还有第二个提醒:如上一节所示,xetex 看的是 CoreText,因此出现在 fc-list 里并不能保证 XeTeX 就能用这个字体。反过来,在 XeTeX 确实经由 fontconfig 查找的构建(通常是 Linux 上的 TeX Live)上,把字体放进用户字体目录后再跑 fc-cache,才是恰当的做法。

updmap — 字体映射的一行到底在说什么

字体映射就是纯文本,每一行把一个 TeX 逻辑名与一个实际文件绑在一起。updmap 并不亲自写这些行,它把updmap.cfg 中列出的 .map 文件连接起来,写出供 dvips 用的 psfonts.map,以及供 pdftexdvipdfmx 用的 pdftex.map。在本机数一下 updmap-sys --listmaps,共有 389 条声明:Map 332 条、MixedMap 46 条、KanjiMap 11 条。连接后的 psfonts.map 是一份 45,672 行、接近 5.5 MB 的文本。其中一行这样读:TeX 侧的名字、PostScript 字体名、重编码指令,以及真正的文件。

terminal
# one real line out of psfonts.map on this machine
ptmr8r NimbusRomNo9L-Regu " TeXBase1Encoding ReEncodeFont " <8r.enc <utmr8a.pfb

updmap-sys --listmaps            # every declared map, and which cfg declared it
sudo updmap-sys --enable Map myfont.map
sudo updmap-sys                  # rebuild psfonts.map and pdftex.map

实际操作中绊住人的,往往不是命令本身,而是你在动哪一份配置。TeX Live 2024 的 updmap 会直接拒绝裸跑:它只回 updmap [ERROR]: Either -sys or -user mode is required.updmap [ERROR]: In nearly all cases you should use updmap -sys.。这种拒绝是好意,因为真正的坑就在其后。updmap --help 自己写得很明白:只要 updmap-user 被执行过哪怕一次,之后再跑 updmap-sys 就不再有任何效果。 因为个人配置文件被创建了,此后由它优先。共享机器与 CI 中统一使用 updmap-sys;只有当你确实想覆盖系统设置时才动 updmap-user。至于为何需要 sudo/usr/local/texlive/2024 之下归 root 所有。

kanji-config-updmap — 选择嵌入 PDF 的日文字体

日文字体的选择之所以拥有独立命令,是有原因的。在 pLaTeX 与 upLaTeX 路线上,「把哪个日文字体嵌入 PDF」并不是在排版时决定,而是在 dvipdfmx 这一步决定的。 因此无需改动稿件,事后也能替换嵌入字体——这个开关就是 kanji-config-updmap(来自 texjporg),它在内部设置 updmapjaEmbed 选项(旧称 kanjiEmbed)。先用 status 看现状。本机返回 CURRENT family for ja: haranoaji (variant: -04),随后把 ipaipaex 等列为待命族。要切换时直接传入族名即可。与 updmap 一样,它也分 -sys-user;共享环境中统一用 kanji-config-updmap-sys 最稳妥。

terminal
kanji-config-updmap-sys status
# CURRENT family for ja: haranoaji (variant: -04)
# Standby family : ipa
# Standby family : ipaex

sudo kanji-config-updmap-sys ipaex   # embed the IPAex family instead
sudo kanji-config-updmap-sys auto    # let it pick from what is installed

luaotfload-tool — LuaTeX 第一次运行为何缓慢

第一次运行 LuaLaTeX 的人,最先吃惊的是等待时间。原因是 luaotfload名称数据库:它扫描系统中的字体文件,建立名称与文件的对应表。本机上它存放在 TEXMFVAR/luatex-cache/generic/names/luaotfload-names.luc.gz,约 390 KB。有意思的是,一旦查不到字体,重建会自动开始。把一个不存在的名字交给 \setmainfont,日志里会出现 luaotfload | db : Reload initiated (formats: otf,ttf,ttc); reason: Font "NoSuchFontXYZ" not found.,随后才停在 ! Package fontspec Error: The font "NoSuchFontXYZ" cannot be found.。所以「刚装的字体 LuaLaTeX 看不到」,往往再跑一次就好了。要显式重建,用 luaotfload-tool --update;若原本没有数据库,它会打印 Font names database not found, generating new one.This can take several minutes; please be patient. 后重新生成。

terminal
luaotfload-tool --update              # rebuild the LuaTeX names database
luaotfload-tool --diagnose=environment # what luaotfload thinks its world looks like
ls "$(kpsewhich --var-value=TEXMFVAR)/luatex-cache/generic/names/"

fmtutil — 重建引擎启动时读取的 .fmt

敲下 pdflatex,引擎会先读入诸如 latex.fmt格式文件,然后才开始干活。那是「读完整个 LaTeX 之后引擎内部状态」的完整快照,正是它让引擎无需每次重读上千行宏就能启动。fmtutil 就是重建这些 .fmt 的工具。它出场的机会不多——只有在做了涉及格式的更新之后,或 .fmt 损坏、引擎还没启动就停住时才用。它不是每装一个宏包就要跑一次的命令。 只重建一个用 fmtutil-sys --byfmt pdflatex,全部重建用 fmtutil-sys --all,后者要花几分钟。-sys-user 的分工与 updmap 完全一致,共享环境统一用 -sys

terminal
sudo fmtutil-sys --byfmt pdflatex   # rebuild one format
sudo fmtutil-sys --all              # rebuild every format (several minutes)
kpsewhich --var-value=TEXMFVAR      # where the .fmt files end up

按症状查命令

命令什么时候用它
fc-list : family想先确认系统里到底有没有这个字体,以及它叫什么名字
luaotfload-tool --update只有 LuaLaTeX 找不到该字体,且再跑一次也不好使
updmap-sys --enable Map手动安装了西文字体宏包,PDF 里的字却没有换过来
kanji-config-updmap-sys statusPDF 中只有日文部分不对;先确认当前的 jaEmbed
fmtutil-sys --byfmt引擎在开始排版之前就因读不到格式而停住
mktexlsr把文件放进了 TEXMFLOCAL 却仍找不到;详见构建工具那一页

最后再提一个位于这张地图边缘的小工具。TeX Live 里带着一条名为 albatross 的命令,据其 man 页说,它用于查找包含指定 Unicode 字形的字体——而且内部依赖 fontconfig,这又是一处「TeX 世界的工具去调用 fontconfig」的关系。不过在本机运行时,它停在了 Unable to locate a Java Runtime.。它需要 Java 运行时,想用就得另行安装。mktexlsrtexhashkpsewhich 在构建工具那一页有详细整理。