若有人问 pdflatex 是什么,准确的答案并不是「一个程序」。它是以预先载入 LaTeX 格式的方式启动的 pdfTeX 引擎。pdflatex、lualatex、platex 这些命令名并排摆着,看上去像是各自独立的软件;实际上,每一个都不过是两条独立轴线的组合——引擎(真正运行的程序)与格式(预先编译好的宏集合)。看清这两条轴,好几件事就一并有了解释:.fmt 文件是什么、为什么运行 latex 得到的是 DVI 而不是 PDF,以及 LaTeX3 最终究竟如何了。
引擎与格式的区别
引擎是可执行文件,格式是数据。 引擎就是读取 .tex 并排版的那个程序——tex、pdftex、xetex、luahbtex、euptex 等等。格式则是把 \documentclass、\section 之类的大量宏收集起来,预先展开并固化成一份转储,也就是 .fmt 文件。启动时引擎瞬间读入这个 .fmt,因此不必每次运行都重新定义成千上万行宏。
因此,「LaTeX」是格式的名字,不是引擎的名字。 同一个 LaTeX 格式可以跑在不同引擎之上,而你敲下的命令名指定的正是某个 引擎+格式的搭配。这张对应表无需猜测:它在 TeX Live 里实实在在地存在,就是那份名为 fmtutil.cnf 的配置文件。它有四列——格式名、引擎名、断词模式文件、参数——TeX Live 2024 版共 54 行,也就是登记了 54 种组合。
$ grep -E '^(tex|latex|pdflatex|lualatex|xelatex|uplatex|amstex|pdfcsplain) ' \
$(kpsewhich fmtutil.cnf)
# format engine hyphenation arguments
tex tex - tex.ini
latex pdftex language.dat *latex.ini
pdflatex pdftex language.dat *pdflatex.ini
xelatex xetex language.dat -etex xelatex.ini
lualatex luahbtex language.dat,language.dat.lua lualatex.ini
uplatex euptex language.dat *uplatex.ini
amstex pdftex - *amstex.ini
pdfcsplain luatex - csplain.ini
pdfcsplain pdftex - csplain-utf8.ini
pdfcsplain xetex - csplain.ini从这张表里可以读出两件事。第一,同一个格式名可以出现在多个引擎之下——pdfcsplain 就有三行,分别对应 luatex、pdftex、xetex。要证明格式与引擎彼此正交,没有比这更好的例子了。第二,参数前面的那个 *:它指示引擎在启用 e-TeX 扩展的状态下转储,而该文件自己的开头注释说明,这本质上等同于 -etex 选项。latex 那一行有 *,Knuth 的 tex 那一行没有——现代 LaTeX 以 e-TeX 为前提这件事,就浓缩在这一个字符里。
.fmt 文件里究竟装着什么
它是把引擎的全部内部状态整个保存下来的内存转储。 既不是源码,也不是文本。宏定义、类别码、断词模式、数学字体分配——latex.ltx 读到最后那一刻引擎记忆中的一切,被原样写成二进制。TeX Live 2024 的 latex.fmt 约 8.2 MB。重新读入这 8.2 MB 与每次重新解释 latex.ltx 之间的时间差,正是 LaTeX 能瞬间启动的原因。
有一个地方,引擎与格式的关系直接呈现在文件系统的形态里:.fmt 并不按「格式名」存放,而是放进以引擎命名的目录中。在 TeX Live 2024 的 texmf-var/web2c/ 之下,并排着 tex/、pdftex/、xetex/、luahbtex/、euptex/ 等目录,各自装着由该引擎转储出的格式。tex/tex.fmt 是 plain TeX,pdftex/latex.fmt 是 LaTeX,euptex/uplatex.fmt 是 upLaTeX——同样是 .fmt 扩展名,但取决于由哪个引擎生成,它们是不同的东西。
# Formats are filed by ENGINE, not by format name.
$ ls /usr/local/texlive/2024/texmf-var/web2c/
aleph euptex hitex luahbtex luajittex luatex metafont pdftex tex xetex
$ ls /usr/local/texlive/2024/texmf-var/web2c/euptex/
eptex.fmt euptex.fmt platex.fmt ptex.fmt uplatex.fmt ...
# Ask kpathsea which .fmt a given engine would load.
$ kpsewhich -engine pdftex latex.fmt
/usr/local/texlive/2024/texmf-var/web2c/pdftex/latex.fmt
$ ls -l $(kpsewhich -engine pdftex latex.fmt)
-rw-r--r-- 1 root wheel 8221690 May 4 2024 .../pdftex/latex.fmt生产端的机制同样简单。给引擎加上 -ini 启动,它就进入不载入任何 .fmt 的「初始化模式」。在这个状态下让它读取 latex.ltx 这样的源文件,最后执行原语 \dump,当时的内部状态便被写成一个 .fmt。曾经是独立可执行文件的 initex,不过是这种 -ini 启动的别名,如今只作为指向 tex 的符号链接留存下来。
用 fmtutil 重建格式
重建 .fmt 的命令是 fmtutil。它读取 fmtutil.cnf,按其中登记的引擎和参数,把格式逐个重新转储。它通常在发行版更新时自动运行,所以很少需要手动敲;但当你换掉了内核文件或断词模式却毫无变化时,原因多半是格式还是旧的。实际会用到的两种写法是 fmtutil-sys --all(全部重建)和 fmtutil-sys --byfmt latex(只重建一个)。
# Rebuild one format (writes into the system tree; needs write permission).
$ fmtutil-sys --byfmt latex
# Rebuild every format listed in fmtutil.cnf.
$ fmtutil-sys --all
# Rebuild only the formats that use a given engine.
$ fmtutil-sys --byengine luahbtex
# Personal tree instead of the system tree (no root needed).
$ fmtutil-user --byfmt pdflatex
# Where did the format end up, and when was it built?
$ kpsewhich -engine pdftex latex.fmt-sys 与 -user 的差别在于写到哪里:系统树(TEXMFSYSVAR)还是各用户自己的树(TEXMFVAR)。有管理员权限时用 -sys 最自然;-user 则适合在共用机器上只替换自己那一份。两者混用是制造「明明改好了却没变」的可靠办法,所以项目里应统一使用其中一种。另外,fmtutil.cnf 本身归发行版管理——它开头的注释明确写着,手工编辑这个文件的改动会在更新时丢失。若要永久更改,请使用它提供的途径,例如 fmtutil-sys --enablefmt。
LaTeX2e:你机器上真正在跑的那个格式
今天说「LaTeX」,几乎无一例外指的是 LaTeX2e。它是 Leslie Lamport 所写 LaTeX 宏集的现行版本,1994 年问世,如今由 LaTeX Project 维护。「2e」表示相对于此前 LaTeX 2.09 的一次小改订,在当时被视为通往筹划已久的 LaTeX3 的过渡步骤。你手上的格式究竟是什么,可以自己核对:内核文件 latex.ltx 自称 \def\fmtname{LaTeX2e},而在 TeX Live 2024 附带的版本中,\fmtversion 为 2023-11-01,\patch@level 为 1。
内核如今按 每年约两次的定期发布 更新。把「最新版是哪个」当成数字背下来毫无意义——几个月就过时了。不如养成习惯:在自己的安装里读一读 latex.ltx 的 \fmtversion,那才是当前实际转储的内核日期。为想提前试用下一版的人,TeX Live 还并行提供了 开发版格式。fmtutil.cnf 中有 latex-dev、pdflatex-dev、xelatex-dev、lualatex-dev、uplatex-dev 等行,敲 pdflatex-dev main.tex 就会用下一版内核排版。它用于检验文档类与宏包的兼容性,不用于正式稿件。
LaTeX3 最终怎么样了:答案是 expl3
名为 LaTeX3 的「格式」,最终没有面世。 面世的是 LaTeX3 的编程语言。这一层叫作 expl3(L3 编程层),它并非以独立的新内核出现,而是被吸收进 LaTeX2e 内部。所以「该不该迁移到 LaTeX3」这个问题没有答案——不是因为无处可迁,而是因为你早就在用了。
转折点是有记录的。LaTeX News 31(2020 年 2 月 2 日)指出,expl3 在此前十年间「已从基本属实验性质转为广泛稳定」,并报告说在那次发布中,团队调整了内核,使其在构建格式时预先载入 expl3 的相当一部分。动机是速度:在 XeLaTeX 或 LuaLaTeX 下载入 fontspec 的文档,每次运行都要为读取 Unicode 数据付出沉重代价。
如今 expl3 已是格式的一部分。读一读内核文件 latex.ltx,就能看到这段逻辑:若 expl3 已经存在,它会报出 Skipping: expl3 code already part of the format 并继续;若不存在,则以 \input expl3.ltx 载入;若连 expl3.ltx 都找不到,就以 \errmessage{LaTeX requires expl3} 停止。换言之,expl3 不再是可选宏包,而是 LaTeX2e 的必需部件。在文档或宏包里写 \RequirePackage{expl3},仍为照顾使用旧格式的人而被推荐,但在当前格式上实际上什么也不做。expl3 本身的语法——\ExplSyntaxOn、\cs_new:Npn、l3kernel 的带类型变量——另有专页讲述。
LaTeX 之外的格式
LaTeX 是使用最广的格式,但并非唯一。fmtutil.cnf 中的 54 行里,属于 LaTeX 一系的只是少数,其余都是别的宏体系。这里值得提一下 amstex。作为可执行文件,amstex 不过是指向 pdftex 的符号链接,而 fmtutil.cnf 里 amstex pdftex - *amstex.ini 这一行,指定的是载入 AMS-TeX 宏的 pdfTeX 引擎。AMS-TeX 是美国数学会在 plain TeX 之上建立的数学排版宏集,而对使用 LaTeX 的人来说,它实际上已被 amsmath 宏包取代。用 AMS-TeX 开始写新文档,几乎没有理由。
| 格式 | 转储它的引擎 | 它是什么 |
|---|---|---|
tex | tex | Knuth 的 plain TeX,唯一不带 e-TeX 扩展的格式 |
latex | pdftex | LaTeX2e,由 DVI 模式的 pdfTeX 运行,输出 .dvi |
pdflatex | pdftex | 同一个 LaTeX2e,直接输出 PDF;西文的事实默认 |
xelatex | xetex | LaTeX2e 跑在 XeTeX 之上;系统字体与 Unicode |
lualatex | luahbtex | LaTeX2e 跑在 LuaHBTeX 之上;可用 Lua 脚本与 HarfBuzz |
platex / uplatex | euptex | 面向日文排版的 LaTeX2e;输出 DVI,再由 dvipdfmx 转为 PDF |
amstex | pdftex | AMS-TeX,建于 plain TeX 上的数学宏;对 LaTeX 用户已被 amsmath 取代 |
cont-en | pdftex / xetex | ConTeXt(MkII),与 LaTeX 分属不同体系的完整格式;登记在两个引擎下 |
csplain | pdftex | 面向捷克语与斯洛伐克语的 plain TeX 衍生格式;构建时显式指定 -etex |
pLaTeX 与 upLaTeX:面向日文的 LaTeX 格式
pLaTeX 与 upLaTeX 是把 LaTeX 格式装到面向日文排版的引擎上。 竖排、注音假名、和文行组版等日文特有的处理,由最初在 ASCII 公司扩展出来的 pTeX 系引擎 承担。查看 TeX Live 2024 的 fmtutil.cnf 可以发现,platex 与 uplatex 都登记在 euptex 引擎 之下,区分它们的是格式的初始化文件(*platex.ini 与 *uplatex.ini)。因此二者之别不在可执行文件,而在格式如何处理字符:platex 保持传统的 pTeX 兼容行为,uplatex 则以内部 Unicode 为前提。新的日文文档,uplatex 是标准选择。
这一系必定输出 DVI,再把含日文的 DVI 用 dvipdfmx 转成 PDF,是两段式流程(uplatex file.tex,然后 dvipdfmx file.dvi)。索引则配合 upmendex。文档类常用奥村晴彦的 jsarticle/jsbook(用于 pLaTeX)及其 Unicode 版本如 ujarticle,以及较新的 jlreq,后者遵循 JIS X 4051 的日文排版要求。引擎内部的细节——JFM 度量、禁则处理、竖排的实现——由「pTeX 家族」那一页负责。
哪条命令对应哪种组合
即便是同一个 LaTeX 格式,命令名也决定了背后的引擎、输出格式以及字体的处理方式。下表请从命令名出发,自左向右阅读。实际工作中最要紧的是最右一列:能否使用 fontspec,直接对应着引擎的选择。
| 命令 | 引擎 | 输出 | Unicode / 系统字体 |
|---|---|---|---|
latex | pdfTeX(DVI 模式) | DVI——需另用工具转换 | 不支持;依赖 inputenc 之类 |
pdflatex | pdfTeX | 直接输出 PDF | 不支持;依赖 inputenc 之类 |
platex | e-upTeX(pLaTeX 兼容配置) | DVI → dvipdfmx | 日文走传统字体配置 |
uplatex | e-upTeX | DVI → dvipdfmx | 内部 Unicode;日文字体仍用传统方式配置 |
lualatex | LuaHBTeX | 直接输出 PDF | 支持——fontspec / luatexja |
xelatex | XeTeX | PDF(内部经由 xdvipdfmx) | 支持——fontspec |
# Engines that write PDF finish in one step.
$ pdflatex main.tex # -> main.pdf
$ lualatex main.tex # -> main.pdf
$ xelatex main.tex # -> main.pdf
# DVI-based routes add a conversion step.
$ latex main.tex # -> main.dvi (pdfTeX in DVI mode)
$ uplatex main.tex # -> main.dvi (Japanese)
$ dvipdfmx main.dvi # -> main.pdf读日志的第一行,然后把组合钉死
日志的第一行同时写着引擎与格式。 把 This is pdfTeX, Version 3.141592653-2.6-1.40.26 (TeX Live 2024) (preloaded format=latex) 拆开看:前半是引擎及其版本,(preloaded format=...) 则是所载入的格式。This is LuaHBTeX ...、This is XeTeX ...、This is e-upTeX ... 也一样读。当协作者报告「同一条命令,输出却不同」时,只需比对这一行,就能立刻区分是编辑器配置的问题,还是 TeX Live 年份或格式的差异。
而一旦组合确定,就把它写进项目里并固定下来。若是学位论文,等到扉页、目录、图表、参考文献都能顺利生成,就把命令记进 README 或 .latexmkrc。中途把文档从 lualatex 换成 uplatex,字体选择、日文处理、生成 PDF 的路径乃至参考文献处理都会一次性改变。若非换不可,请在回到正式稿件之前,用一个小的试验文件把所有功能都验证一遍。至于当初该如何选择的依据——速度、宏包兼容性、日文的具体情况——都汇总在「如何选择引擎」那一页。