宏包与字体管理

决定一套 LaTeX 环境能否体面地用下去的,其实不是背熟 tlmgr 的选项表,而是懂得 该把文件放在哪里。把 TeX 用的搜索路径 TEXMF 打印出来,会看到有些目录树前面带着 !!,有些没有。这个记号的意思是「别扫磁盘,只查 ls-R 数据库」,而这两个字符决定了 你手工放进去的文件是立刻被找到,还是要等你敲一次 mktexlsr 才现身。本页讲用 tlmgr 添加与更新宏包、用户目录树 TEXMFHOME 与系统目录树 TEXMFLOCAL 的分工、从 CTAN 手工安装的步骤,以及字体映射(updmap)的管理,所有数字都来自一台真实的 TeX Live 2024。

添加宏包:tlmgr install 怎么用

给 LaTeX 添加宏包 的标准做法就一行:tlmgr install <名字>,依赖默认一并带上。可以一个个点名,也可以整包拉进来,比如 collection-fontsrecommended。若装进系统级目录树,需要管理员权限(Unix 上 sudo tlmgr install …,Windows 上用管理员 shell)。不想拉依赖时加 --no-depends;想把出问题的文件换掉时加 --reinstall

terminal
# Install a package; dependencies come along by default
tlmgr install siunitx
tlmgr install collection-fontsrecommended

# Skip dependencies, or overwrite a broken install
tlmgr install --no-depends siunitx
tlmgr install --reinstall siunitx

# A system-wide tree needs admin rights
sudo tlmgr install siunitx

实务上更要紧的,是 从报错信息走到宏包名字的那条路。碰上 LaTeX Error: File siunitx.sty not found 之类的报错时,随手把搜索到的 .sty 丢进稿件目录是最糟的一招。先把 文件名宏包名 分开:文件是 siunitx.sty,宏包是 siunitx——通常一致,但常常不一致,比如 tikz.sty 其实在 pgf 宏包里。tlmgr search --global --file 回答的正是「哪个宏包收录了这个文件」,这就是那座桥。名字只记得一半就用 tlmgr search --global <词>;想弄清一个宏包的来历,tlmgr info <名字> 会打印说明、许可证、大小与收录文件。

terminal
# From "File siunitx.sty not found" to an installed package
kpsewhich siunitx.sty                      # is it already here?
tlmgr search --global --file siunitx.sty   # which package ships it?
tlmgr install siunitx                      # install it properly

# Half-remembered name, or curious about a package
tlmgr search --global siunit
tlmgr info siunitx

有一个容易漏掉的行为:tlmgr installtlmgr remove不会刷新 /usr/local/bin 之类系统目录里的符号链接。如果你的 PATH 直接指向 TeX Live 自己的 bin 目录,这就无关紧要;但若你用 tlmgr path add 布置可执行文件、man 页与 info 页的链接,那么在装了带新可执行文件的宏包之后,就得自己再跑一次 tlmgr path add(删掉之后则是 tlmgr path remove)。不喜欢敲命令的话,TeX Live 还附带 Tcl/Tk 写的图形界面 tlshell,搜索、安装、删除、批量更新、切换仓库都能在窗口里完成,而且会显示底层跑过的命令日志,是入门 tlmgr 的温和途径。

更新:--self 与 --all 要成对

标准做法就一行:tlmgr update --self --all——--selftlmgr 自身(基础设施)更新到位,--all 更新其余全部。这个顺序不是口味问题。tlmgr 的手册对 --all 写得很清楚:它更新除 tlmgr 自身之外的所有已装宏包;若 tlmgr 自身有更新,除非同时给出 --force--self,否则会报错。也就是说,过期的 tlmgr 会停下来,坚持先把自己换掉。两个开关一起给,它就照做:先更新自己,成功后以新版本继续完成余下的更新。

terminal
# The standard move: infrastructure first, then everything else
tlmgr update --self --all

# See what would change before committing to it
tlmgr update --list

# Update one package only
tlmgr update siunitx

还该知道这次更新顺带做了什么。在服务器端被移出某个 collection 的宏包会被 自动删除,而新加进你已有 collection 的宏包会被 自动安装;加 --list 可以在动手前把两者都看清。从哪里取,由 仓库 设置决定,默认走 CTAN 的镜像自动分流。线路慢,或想钉死某个镜像,就用 tlmgr option repository 永久切换;只想这一次换个镜像,则加 --repository <url>。纸张尺寸这类默认值同样归 tlmgr 管,所以 tlmgr paper a4 会把所有工具一次改成 A4(按程序设则是 tlmgr dvips paper a4)。当前设置用 tlmgr option show 一次看全。

terminal
# Where tlmgr fetches from, and other stored defaults
tlmgr option repository ctan          # automatic CTAN mirror redirection
tlmgr option repository https://example.org/CTAN/systems/texlive/tlnet
tlmgr option show

# Use a different mirror for one run only
tlmgr install --repository https://example.org/CTAN/systems/texlive/tlnet siunitx

# Paper size defaults are stored here too
tlmgr paper a4
tlmgr dvips paper a4

TEXMFHOME 与 TEXMFLOCAL 的区别

那些不归 tlmgr 管的文件——会议模板的类文件、自己写的样式、从 CTAN 手工下载的宏包——有两个去处:TEXMFHOME 只归你自己,TEXMFLOCAL 归整台机器。两者都排在发行版自己的树(TEXMFDIST之前被搜索,所以同名文件放这里就赢。实际路径因环境而异,别想当然,打印出来看。在这台 Mac 上敲 kpsewhich -var-value=TEXMFHOME 返回的是 /Users/wedd/Library/texmf,而不是 ~/texmf。Linux 上默认才是 ~/texmf,所以 照搬 Linux 的教程到 Mac 上,放进去的文件会永远找不到。这台机器上的 TEXMFLOCAL/usr/local/texlive/texmf-local,值得留意的是它 位于年份目录之外——所以换到明年的版本时,放在那里的东西照样活着。

terminal
# Never guess these paths - print them
kpsewhich -var-value=TEXMFHOME    # your own tree (differs on macOS and Linux)
kpsewhich -var-value=TEXMFLOCAL   # machine-wide tree, outside the year directory
kpsewhich -var-value=TEXMFDIST    # the distribution itself - do not edit by hand

# The whole search order, including the !! markers
kpsewhich -expand-path='$TEXMF'

在两者之间怎么选很简单:为自己稿件准备的文件放 TEXMFHOME;共享机器上要让所有人都能用的放 TEXMFLOCALTEXMFHOME 不需要管理员权限,重装系统时随家目录一起搬走。目录内部遵循与发行版相同的布局(TDS):LaTeX 的样式放在 tex/latex/ 之下。这套布局的全貌交给「目录结构与路径」那一页;实务上,记住下面代码里的层级基本就够用了。有一点要提醒:别在稿件目录里留一份旧的 .sty 副本。TeX 最优先搜索当前目录,于是即便 tlmgr 更新了正主,那份陈旧副本仍会一直赢,制造出极难排查的故障。

terminal
# A personal class file, in the tree that belongs to you
HOME_TREE="$(kpsewhich -var-value=TEXMFHOME)"
mkdir -p "$HOME_TREE/tex/latex/local"
cp mythesis.cls "$HOME_TREE/tex/latex/local/"
kpsewhich mythesis.cls    # should print the path immediately

# Let tlmgr manage that tree too, with no root required
tlmgr init-usertree
tlmgr --usermode install siunitx

mktexlsr / texhash 到底什么时候需要

答案很干脆:手工往 TEXMFLOCAL 或系统树里放了文件就需要,放进 TEXMFHOME 就不需要。原因就是开头说的那个 !!。在这台机器上展开 TEXMF/usr/local/texlive/texmf-localtexmf-dist 前面带着 !!,而 ~/Library/texmf 前面没有。!! 是给 kpathsea 的指令:「这棵树别看磁盘,只信 ls-R 数据库」,所以 只要不刷新 ls-R,新文件就等于不存在。旁证是:记录哪些树带 ls-R 的 TEXMFDBS 里,压根没有 TEXMFHOME。这个索引也确实值回票价——这台机器上的 texmf-dist/ls-R 有 276,953 行,正是靠它,TeX 才不必每次都去走一遍 22 万多个文件。

重建索引的命令是 mktexlsr,它会为 TEXMFDBS 里列出的每棵树重新生成 ls-Rtexhash 不是另一个命令——在这台机器上一验便知,texhash 是指向 mktexlsr 的符号链接,也就是同一个程序的两个名字,随便用哪个都行。因为要写系统目录树,所以需要 sudo。另外,tlmgrgetnonfreefonts 会自己把该刷新的刷新掉,所以实务上 你需要亲手敲它的场合,只有「自己手工拷贝了文件」之后

terminal
# The same file in the system tree DOES need the index rebuilt
LOCAL_TREE="$(kpsewhich -var-value=TEXMFLOCAL)"
sudo mkdir -p "$LOCAL_TREE/tex/latex/local"
sudo cp mythesis.cls "$LOCAL_TREE/tex/latex/local/"
sudo mktexlsr          # texhash is a symlink to this same program

kpsewhich mythesis.cls # now it resolves
kpsewhich -all mythesis.cls  # and shows every copy, if several collide

从 CTAN 手工安装:.dtx 与 .ins

只有在 tlmgr 里没有的东西才需要手工:投稿方自行分发的专用类文件、上周才发布的宏包、只在作者站点上存在的版本。CTAN 上有些宏包直接放着 .sty,但很多是成对发布的——.dtx 把源码与文档写在一起,.ins 则是取出源码的指示文件。这时跑一次 tex foo.ins,就会从 .dtx 里生成 foo.sty,再把它拷到 TEXMFHOME 下的合适位置。.dtx 这种格式正是文学式编程最纯粹的样子:跑 pdflatex foo.dtx,会得到一份带注释源码的 PDF 手册。

terminal
# A package that ships as .dtx + .ins: extract, then place
tex foo.ins                     # writes foo.sty (and foo.cls, if any)
pdflatex foo.dtx                # optional: build the annotated manual

HOME_TREE="$(kpsewhich -var-value=TEXMFHOME)"
mkdir -p "$HOME_TREE/tex/latex/foo"
cp foo.sty "$HOME_TREE/tex/latex/foo/"
kpsewhich foo.sty

MiKTeX 这边:mpm 与自动安装

MiKTeX 里没有 tlmgr。命令行归 mpm(MiKTeX Package Manager),图形界面归 MiKTeX Console,更新与装包都走这两处。最大的差别是 即时安装:文档要求而本机没有的宏包,会在排版途中下载并让编译继续(Console 里可选「总是安装/每次询问/从不」)。不过这套自动化有个附带条件:即便 MiKTeX 是为所有用户安装的,即时取来的宏包仍会落进当前运行用户自己的 AppData\Roaming 目录。共享机器上,稳妥的做法是先用管理员模式把需要的宏包装齐。

字体映射的管理:updmap 与 getnonfreefonts

字体映射 是一张逐行写就的对照表,说明「TeX 用这个名字叫的字体,实际上是哪个文件,以及该怎样嵌进 PDF」。pdftexdvipdfmxpdftex.mapdvipspsfonts.map,日文另有 kanjix.map,等等。这些表不是手写的,而是 updmap 生成。想感受一下规模,看行数最快:这台机器上的 pdftex.map45,443 行。装上字体宏包并让 updmap 跑过之后,这张表才会更新,那款字体也才谈得上嵌入 PDF。

updmap 有两副面孔——updmap-sys 管全系统,updmap-user 只管你自己——而 TeX Live 配置里最扎人的坑就埋在这儿。updmap --help 自己就警告过:只要运行过一次 updmap-user,此后再运行 updmap-sys 就完全没有效果了。因为会生成一份个人配置文件,从此把系统那份遮住。updmap-sys 遇到这种情况会给出警告,但若不知道缘由,你会为「明明用管理员权限跑了,字体却纹丝不动」苦恼很久。稳妥的规矩是:只用 updmap-sys,绝不碰 updmap-user

中日韩字体的嵌入有专门的入口:kanji-config-updmap-sys。它在底下调用 updmap,用来切换把哪一族 CJK 字体嵌进 PDF。传给它 status 就会报告现状——这台机器上当前的日文族是 haranoaji,可切换的候选是 haranoajiipaipaex。换句话说,TeX Live 内附的日文字体是原之味(Harano Aji)与 IPA / IPAex,而 Noto CJK 与思源黑体(Source Han)并不在内。要用后者,就得让它引用操作系统里已装的字体,或者另行安装。

terminal
# Ask, without changing anything: which CJK family is embedded?
kanji-config-updmap-sys status

# Switch the embedded Japanese family, machine-wide
sudo kanji-config-updmap-sys haranoaji

# Rebuild the map files after a manual font install
sudo updmap-sys        # never updmap-user: it permanently shadows updmap-sys

最后说说 不能随发行版一起走的字体。授权写着「可自由使用但不得出售」的字体,无法打包进 TeX Live,因为它也以出售的 DVD 形式发行。TUG 的 getnonfreefonts 脚本正是为此存在:它从 CTAN 取来字体本体与 TeX 支持文件,并配置到 TeX 能找到为止。关键在于 --user(放进 TEXMFHOME)与 --sys(放进系统树)的分工。旧文里出现的 getnonfreefonts-sys 这个独立命令如今已经不存在——要给机器上所有人用,就以管理员权限运行 getnonfreefonts --sys …。能装哪些,用 --lsfonts 列出来看。

terminal
# See what is on offer, then install into your own tree
getnonfreefonts --user --lsfonts
getnonfreefonts --user luximono

# Or system-wide, for everyone on the machine
sudo getnonfreefonts --sys luximono
  • 通用宏包用 tlmgr 装。 留在稿件目录里的旧副本,即使更新过也会继续赢,制造出无人能解释的故障。
  • 只属于某个会议模板的文件可以放在稿件仓库里,因为它们属于那份投稿包,而不属于发行版。
  • 在共享机器与 CI 上,把 TeX Live 的年份、额外装的宏包名、固定的仓库或 Docker 标签写进 README 或 .latexmkrc「我在自己笔记本上装过一次」不叫可复现。
  • 交稿前不要跑 tlmgr update --self --all 那等于用与昨天不同的宏包去生成 PDF。更新请挑别的日子试。
  • 绝不要用 updmap-user 哪怕只跑过一次,updmap-sys 就此失效,而且事后极难查出缘由。