TeX Live 2024 的 bin 目录里并列着六个命令:ptex、uptex、eptex、euptex、platex、uplatex。可是用 ls -l 一看,六个全都是指向同一个可执行文件 euptex 的符号链接。为日语而生的这一系列 LaTeX 引擎——pTeX、upTeX、e-pTeX、e-upTeX——用了四分之一个世纪汇成一条实现。本页顺着这条汇流走,一路读实际的横幅信息、日志和实测数值。竖排的规矩与避头尾的细节留给别的页面,这里负责的是引擎本身。
六个命令,一个可执行文件
根本不存在名为 platex 的可执行文件。 在 TeX Live 2024 中,platex 是指向 euptex 的符号链接,ptex、uptex、eptex、uplatex 也一样。那么区别在哪里——在于各自加载的格式。TeX 系列的程序启动时会看自己被调用时的名字,去加载同名的格式文件(.fmt)。这张对应表就是 fmtutil.cnf,其中并排写着 platex euptex language.dat *platex.ini 和 uplatex euptex language.dat *uplatex.ini 两行。第二列是引擎,第四列是烧制该格式所用的 ini 文件。换句话说,pLaTeX 与 upLaTeX 是同一个引擎 e-upTeX 之上的两种不同格式。
$ ls -l /usr/local/texlive/2024/bin/universal-darwin/ | grep -E "ptex|platex"
lrwxr-xr-x platex -> euptex
lrwxr-xr-x ptex -> euptex
lrwxr-xr-x uplatex -> euptex
lrwxr-xr-x uptex -> euptex
lrwxr-xr-x eptex -> euptex
-rwxr-xr-x euptex # the only real binary
$ grep -E "^(platex|uplatex|ptex|uptex|eptex) " texmf-dist/web2c/fmtutil.cnf
platex euptex language.dat *platex.ini
eptex euptex language.def *eptex.ini
ptex euptex - ptex.ini
uplatex euptex language.dat *uplatex.ini
uptex euptex - uptex.ini把六个都跑一遍,只留第一行,这个结构就一目了然。横幅的主体每次都一样——This is e-upTeX, Version 3.141592653-p4.1.1-u1.30-230214-2.6 (TeX Live 2024)——变的只有括号:platex 一侧打印 (utf8.euc),uplatex 一侧打印 (utf8.uptex)。这个括号标明的是内部的日文编码,也是区分 pTeX 谱系与 upTeX 谱系的唯一外部线索。常有人问「明明执行的是 platex,怎么显示 e-upTeX」——并没有出错,这正是正常的样子。
$ for c in ptex uptex eptex euptex platex uplatex; do
> printf "%s: " $c; echo "\\end" | $c | head -1
> done
ptex: This is e-upTeX, Version 3.141592653-p4.1.1-u1.30-230214-2.6 (utf8.euc) (TeX Live 2024) (preloaded format=ptex)
uptex: This is e-upTeX, Version 3.141592653-p4.1.1-u1.30-230214-2.6 (utf8.uptex) (TeX Live 2024) (preloaded format=uptex)
eptex: This is e-upTeX, Version 3.141592653-p4.1.1-u1.30-230214-2.6 (utf8.euc) (TeX Live 2024) (preloaded format=eptex)
platex: This is e-upTeX, Version 3.141592653-p4.1.1-u1.30-230214-2.6 (utf8.euc) (TeX Live 2024) (preloaded format=platex)
uplatex: This is e-upTeX, Version 3.141592653-p4.1.1-u1.30-230214-2.6 (utf8.uptex) (TeX Live 2024) (preloaded format=uplatex)横幅里的 p4.1.1-u1.30-230214-2.6 是什么意思
那一长串数字并不是一个版本号,而是把四条各自独立的谱系拼接在一起。开头的 3.141592653 是原始 TeX,p4.1.1 是 pTeX,u1.30 是 upTeX,230214 是 e-pTeX(日期形式),末尾的 2.6 是 e-TeX。这不是猜测——引擎把每一项都做成了原语,可以在文档里用 \the\ptexversion 之类直接读出来。会读这一行之后,e-pTeX 是什么 这个问题就有了具体答案:它是把 e-TeX 并入 pTeX 的产物,而此刻运行的 e-upTeX 又在其上叠了 upTeX,一共四层。
% read the version primitives back out of the running engine
\typeout{PTEX=\the\ptexversion.\the\ptexminorversion\ptexrevision}
\typeout{UPTEX=\the\uptexversion\uptexrevision}
\typeout{EPTEX=\the\epTeXversion}
\typeout{ETEX=\the\eTeXversion\eTeXrevision}
% PTEX=4.1.1 UPTEX=1.30 EPTEX=230214 ETEX=2.6| 横幅片段 | 来自哪里 | 如何读回 |
|---|---|---|
3.141592653 | 高德纳的原始 TeX | 只出现在横幅上,没有对应原语 |
p4.1.1 | pTeX——源自 ASCII 的日文扩展 | \ptexversion、\ptexminorversion、\ptexrevision |
u1.30 | upTeX——Unicode 化的改造 | \uptexversion、\uptexrevision |
230214 | e-pTeX——用日期当版本号 | \epTeXversion |
2.6 | e-TeX 扩展 | \eTeXversion、\eTeXrevision |
pTeX 往引擎里埋进了什么——排版方向与 JFM
pTeX 之所以必须是对引擎的改造而不是一套宏,有两个原因:排版方向(\tate 与 \yoko)和 JFM(和文字体度量)。开发者是ASCII 公司——日本那家以电脑与游戏杂志闻名的出版社,名字里的 p 取自 publishing,目标正是做出能用于商业出版的日文 TeX。排版方向听上去只是文字朝哪边排,但在 TeX 内部,这是一桩盒子尺寸的含义整个对调的事件。实测一下,数字会直说。
把同样的「あいう」放进 \hbox,比较横排与竖排下的尺寸。横排时高 8.46753pt、深 1.15465pt——正是坐在基线上的那种不对称形状,和西文一样。竖排时高与深都变成 4.8111pt,也就是全角宽 9.6222pt 的恰好一半。竖排中字符根本不落在基线上,而是跨在栏的中心线上。能在引擎内部切换这套尺寸解释,正是 pTeX 的核心,也是宏包学不来的部分。
\documentclass{utarticle} % ut... = upLaTeX, vertical writing
\begin{document}
\setbox0=\hbox{あいう}
\typeout{TATE w=\the\wd0\space h=\the\ht0\space d=\the\dp0}
\yoko
\setbox0=\hbox{あいう}
\typeout{YOKO w=\the\wd0\space h=\the\ht0\space d=\the\dp0}
\end{document}
% TATE w=28.86658pt h=4.8111pt d=4.8111pt
% YOKO w=28.86658pt h=8.46753pt d=1.15465pt空隙住在哪里——\kanjiskip 与 \xkanjiskip
和文的字间距不属于字形宽度,而是作为胶(可伸缩的空白)加进去的。pTeX 系用两个寄存器保存它:和文与和文之间是 \kanjiskip,和文与西文相接处是 \xkanjiskip。在 ujarticle(正文 10pt)下实测,\kanjiskip 为 0.0pt plus 0.4pt minus 0.5pt——自然宽度为零但可伸缩,于是成了断行时可调剂的余量。而 \xkanjiskip 是 2.40555pt plus 1.0pt minus 1.0pt,这个看着零碎的 2.40555,正是全角宽 9.6222pt 除以四,也就是四分之一个字宽。「日文与西文单词之间留四分之一空」这条排版惯例,就这样直接摆在寄存器的默认值里。
\documentclass{ujarticle}
\begin{document}
\typeout{KANJISKIP=\the\kanjiskip}
\typeout{XKANJISKIP=\the\xkanjiskip}
\setbox0=\hbox{あ}\typeout{EM=\the\wd0}
\setbox0=\hbox{あAい}\typeout{WITH=\the\wd0}
{\xkanjiskip=0pt \setbox0=\hbox{あAい}\typeout{WITHOUT=\the\wd0}}
\end{document}
% KANJISKIP=0.0pt plus 0.4pt minus 0.5pt
% XKANJISKIP=2.40555pt plus 1.0pt minus 1.0pt
% EM=9.6222pt WITH=31.5555pt WITHOUT=26.7444pt
% 31.5555 - 26.7444 = 4.8111 = 2 x 2.40555| 寄存器 | TeX Live 2024 实测值 | 决定什么 |
|---|---|---|
\kanjiskip | 0.0pt plus 0.4pt minus 0.5pt | 和文之间的空隙;自然宽度为零,只留伸缩 |
\xkanjiskip | 2.40555pt plus 1.0pt minus 1.0pt | 和文与西文相接处的空隙;正好是 9.6222pt 全角的四分之一 |
\prebreakpenalty | 对顿号为 10000 | 不得出现在行首的字符;10000 表示绝不断开 |
\postbreakpenalty | 对左直角引号为 10000 | 不得出现在行尾的字符;避头尾以罚值表达 |
\jcharwidowpenalty | 500 | 抑制段落末行只剩一个和文字符 |
这里值得记住的是:避头尾并非编码为「规则」,而是编码为一个数值。TeX 断行的做法是把各候选断点的代价相加取最小,所以只要给顿号一个 10000 的上限罚值,「绝不在此处起行」就成了绝对的。反过来,把数值调小,禁则就松了。而 JFM 是一种度量,它除了记录每个和文字形的宽、高、深,还记录哪两类字符之间该留多少空,标点与括号周围的收紧正出自这里。个别约物的调法、以及 \inhibitglue 用在哪里,则归日文排版的页面管。
pTeX 与 upTeX 的区别——一个字就能分出来
差别归根到底只在内部使用的字符编码这一点。pTeX 内部用 EUC-JP 或 Shift_JIS,能处理的汉字实际上被绑在 JIS 的范围内。upTeX 内部是 Unicode,没有这道栅栏。抽象地说不好懂,用一个字试最快——所谓「梯子高」髙(U+9AD9)。喂给 platex,运行会停在 ! LaTeX Error: Unicode character ^^e9^^ab^^99 (U+9AD9) not set up for use with LaTeX.。把同一个字交给 uplatex(用 \documentclass[uplatex]{jsarticle}),则一路跑到 Output written on ... .dvi。满是人名地名的文档在 platex 下突然报错,原因几乎总是这个。
$ platex kanji.tex # \documentclass{jsarticle} + 髙
! LaTeX Error: Unicode character ^^e9^^ab^^99 (U+9AD9)
not set up for use with LaTeX.
$ uplatex kanji2.tex # \documentclass[uplatex]{jsarticle} + 髙
Output written on kanji2.dvi (1 page, 304 bytes).upTeX 自 2007 年起由 田中琢尔(Takuji Tanaka) 开发。TeX Live 附带的 uplcore.ltx 文件头至今仍留着 Copyright (c) 2016 Takuji Tanaka 这一行,为这条支脉的出处作证。upTeX 设计中最见效的是 \kcatcode 机制:可以按字符区段切换「按和文处理/按西文处理」。因此 upTeX 不仅能排日文,还能在同一份文档里同时排简体中文、繁体中文与韩文谚文——TeX Live 的 tlmgr info uptex 说明中就明确写着能同时处理这四种文字。这是 pTeX 时代够不到的领域。
e-pTeX 从哪里来——一份大学课程作业报告
e-pTeX 的起点,是 北川弘典 为大学实习课「计算数学 II」提交的一份作业记录。听着难以置信,但那份文档至今仍随 TeX Live 一起发行,文件名 eptex_resume.pdf——封面写着「计算数学 II 作业记录」,组名是 epsilon。同一份报告还记下:角藤亮在 2007 年 12 月 30 日做过把 pTeX 与 e-TeX 合起来的实验,并称之为 peTeX(作者顺带写道,p 与 e 位置反过来这点挺有意思)。TeX Live 的 ptex-manual 包在 README 里声明,这类旧资料收录进来「仅出于历史原因」。如今每天编译着成千上万份日文文档的引擎,其创始文件竟是一份学生作业。
那么 e-TeX 扩展究竟加了什么?\numexpr 之类的整数运算、防止宏在不该展开处展开的 \protected、大幅增加的寄存器数量,以及双向排版的基础设施。都不起眼——但如今大量 LaTeX 宏包正是以它们存在为前提写成的。于是日文生态里越来越多东西离了 e-TeX 就跑不动,e-pTeX / e-upTeX 便成了默认。没有 \protected,放进 \section 参数里的命令就会展开进目录并把它弄坏;这是每天都在发生的事,不是理论上的顾虑。这场汇流的终点,正是开头那幅景象:六个命令,一个 euptex。
现在由谁在维护
是 日本語 TeX 開発コミュニティ(Japanese TeX Development Community,通称 texjporg)。从企业交到社群手上的那一刻,就刻在文件的版权声明里。TeX Live 2024 所含 plcore.ltx(pLaTeX 的核心)开头两行是 Copyright (c) 2010 ASCII MEDIA WORKS 与 Copyright (c) 2016-2020 Japanese TeX Development Community,其下接着 This file is part of the pLaTeX2e system (community edition).。upLaTeX 那边的 uplcore.ltx 在同一位置并列了三方:ASCII MEDIA WORKS、Takuji Tanaka、Japanese TeX Development Community。日文 TeX 从出版社到个人再到共同体的三十年,就折叠在这三行注释里。
在这条谱系之外还有 pTeX-ng(命令名为 ApTeX,Clerk Ma 的重新实现)。它备齐 e-TeX、pTeX、upTeX 的原语以保持与 e-upTeX 兼容,同时把内部扩展到整个 Unicode,并内嵌把 dvipdfmx 库化而成的 libdpx,从而不经 DVI 直接输出 PDF。不过老实说:它并不在 TeX Live 2024 里。 tlmgr info ptex-ng 会回答 cannot find package ptex-ng。(存在的痕迹倒是有:ctex 宏包附带了名为 ctex-engine-aptex.def 的引擎定义文件。)实务上的默认仍是 e-upTeX,pTeX-ng 属于另行安装来试的对象。
| 引擎 | 内部字符编码 | e-TeX | 在 TeX Live 2024 中的实体 |
|---|---|---|---|
pTeX | EUC-JP / Shift_JIS | 无 | 没有独立可执行文件;ptex 是指向 euptex 的符号链接 |
upTeX | Unicode(UTF-8) | 无 | 同上;uptex 也是指向 euptex 的符号链接 |
e-pTeX | EUC-JP / Shift_JIS | 有 | eptex 格式;横幅显示 (utf8.euc) |
e-upTeX | Unicode(UTF-8) | 有 | 唯一真正的二进制;platex 与 uplatex 都跑在它上面 |
pTeX-ng / ApTeX | Unicode(至 0x10FFFF) | 有 | 未随发行版提供;tlmgr info ptex-ng 报告找不到 |
实务——从 uplatex 经 dvipdfmx 到 PDF
pTeX 系不会直接吐出 PDF。它的输出永远是 DVI,再由 dvipdfmx 转成 PDF。这是与直接写出 PDF 的 pdfTeX、XeTeX、LuaTeX 的决定性区别;不知道这点,就会卡在「跑了 uplatex 却没有 PDF」上。标准流程是 uplatex 跑两遍(让交叉引用和目录稳定),再跑一遍 dvipdfmx。另外,在 TeX Live 中 dvipdfmx 本身也是指向 xdvipdfmx 的符号链接,用 ls -l 一看名字变了不必吃惊,这同样正常。
\documentclass{ujarticle} % uj... = upLaTeX, horizontal; ut... = vertical
\begin{document}
こんにちは、\LaTeX。日本語が美しく組めます。
\[ E = mc^2 \]
\end{document}$ uplatex document.tex # pass 1 -> document.dvi + document.aux
$ uplatex document.tex # pass 2 -> cross-references settle
$ dvipdfmx document.dvi # -> document.pdf- 没有 PDF 时,先看
.dvi在不在。在,问题就出在dvipdfmx一侧;不在,就出在uplatex一侧。这一步就能把排查范围砍掉一半。 - 出现乱码,依次检查源文件是否存为 UTF-8、文档类是否面向 upLaTeX(
ujarticle/utarticle,或\documentclass[uplatex]{jsarticle})。引擎一侧的开关是-kanji=STRING(输入与输出,euc|jis|sjis|utf8|uptex)和-kanji-internal=STRING(内部编码,euc|sjis|uptex)。 - 提问或报缺陷时,务必附上三样:命令名(
platex还是uplatex)、日志第一行的横幅、源文件的保存编码。光凭横幅里是(utf8.euc)还是(utf8.uptex),问题就定了一半。 - 要新开一份文档,若无硬性模板,就用 UTF-8 源文件加
uplatex、dvipdfmx,文档类取jlreq或学校指定的那一个。既有模板若以platex为前提,就照它的编译步骤走,不要为赶时髦而改写。 - 想自由挑字体,别在 pTeX 一路死磕,可以考虑 LuaLaTeX(LuaTeX-ja)或 XeLaTeX(xeCJK):能直接按名字指定系统 OpenType 字体,而且一步就出 PDF。
归结起来,判断可以这样折起来:要以成熟与速度稳妥地排日文论文,就走 e-upTeX(uplatex)→ dvipdfmx;需要系统字体或 Lua 层扩展,就用 LuaLaTeX;手上已有 XeTeX 资产,就用 XeLaTeX 加 xeCJK。另外别把 pTeX 系的「老」当成缺点。正是 1980 年代那个把竖排与 JFM 放进引擎内部的决定,才让日文商业出版能在 TeX 上成立。今天你敲下 uplatex 时,启动的是那个决定的第四十年。