日文排版方法

用 LaTeX 做日文排版,第一个要定的不是加载哪个宏包,而是走哪条路线。今天活着的有三条:upLaTeX + dvipdfmxLuaLaTeX + LuaTeX-jaXeLaTeX + zxjatype。同一个 .tex 三条路都能走通,三条路都会把同一款正文字体嵌进 PDF。区别在于速度、还能用哪些宏包,以及事故发生在哪里。日文正是 TeX 长出第二套排版模型的原因——逐字胶、竖排、禁则——而这三条路线就是它的三种实现。本页用 TeX Live 2024 上实测的数字把选择定下来。

日文 LaTeX 的三条路线:日文知识放在哪里

三者真正的差别在于日文排版的知识被放在系统的哪一层。在 upLaTeX 中,知识在引擎内部:竖排、禁则、字间胶都是一个用 C 写成的排版器的一部分,所以它快。在 LuaLaTeX 中,知识在 Lua 回调里——LuaTeX-ja 遍历 LuaTeX 刚构造出的节点表并施加日文规则,所以它慢,但也远更容易改写。在 XeLaTeX 中,知识在一个宏包 zxjatype 里(内部加载 xeCJK),那是为日文调校过的通用 CJK 机制,而不是日文引擎。输出方式也不同:只有 upLaTeX 经由 DVI 再交给 dvipdfmx;另外两条直接写出 PDF。

路线引擎输出日文字体
uplatex + dvipdfmxe-upTeX(pTeX 系)DVI → PDF经由 TFM/VF;嵌入由 dvipdfmx 负责
lualatex + luatexjaLuaTeX直接 PDF直接使用 OpenType;用 \setmainjfont 指定
xelatex + zxjatypeXeTeXXDV → PDF(xdvipdfmx)也可用系统字体;常配 zxjafont

这张表的右下角藏着日文 TeX 的一段历史。dvipdfmx 起初是把 Mark A. Wicks 的 dvipdfm 扩展为支持多字节与 CID 的产物——一件为日文而造的工具。后来 XeTeX 把它拿来当自己的输出阶段,于是有了 xdvipdfmx。在实际的 TeX Live 2024 安装里,dvipdfmx 是指向 xdvipdfmx符号链接。一个为日文而写的转换器,如今承担着所有 XeTeX 文档的最后一道工序,无论文档里有没有日文。

速度差多少:同一份原稿走三条路线

在 TeX Live 2024 上编译同一份十页日文文档,uplatex + dvipdfmx 用了 0.31 秒lualatexltjsarticle)用了 0.59 秒xelatexbxjsarticle + zxjatype)用了 0.74 秒。大致是 1 : 1.9 : 2.4,这个比例在单页文件上也差不多——差距主要来自启动和排版本身,而不是文档长度。0.3 秒与 0.6 秒之差,跑一次谁都不在意;但用 latexmk -pvc 每次保存都重编译的人会感觉到。这正是 pLaTeX 路线二十多年后仍未消失的原因,而不只是念旧。三份 PDF 都子集嵌入了同一款 Harano Aji Mincho,用 pdftotext 都能取出正确的日文,所以快的那条并不是质量低的那条。

terminal
# route 1 -- fastest; DVI in between
uplatex main.tex && dvipdfmx main.dvi
ptex2pdf -l -u main            # the same two steps in one command

# route 2 -- LuaTeX-ja, PDF straight out
lualatex main.tex

# route 3 -- XeTeX via zxjatype
xelatex main.tex

文档类怎么选:jsclasses、jlreq、ltjsclasses、bxjscls

路线一定,文档类几乎就自动定了:upLaTeX 用 jsarticle(来自 jsclasses)或 jlreq,LuaLaTeX 用 ltjsarticle(来自 ltjsclasses)或 jlreq,XeLaTeX 用 bxjsarticle(来自 bxjscls)。但有一个例外——jlreq 支持 pLaTeX、upLaTeX 和 LuaLaTeX,却不支持 XeLaTeX。把 \documentclass{jlreq} 交给 xelatex,会得到 ! Undefined control sequence.,接着一路滚到 ! LaTeX Error: Missing \begin{document}.。"固定文档类就能自由换引擎"这句话是对的,但可换的引擎里不包括 XeLaTeX。

文档类可用引擎性格
jsarticlepLaTeX / upLaTeX奥村晴彦的 jsclasses(书籍用 jsbook);事实标准,历史文档最多
jlreqpLaTeX / upLaTeX / LuaLaTeX依 W3C 的 JLReq 设计;竖排只需一个 tate 选项
ltjsarticle仅 LuaLaTeXjsclasses 移植到 LuaTeX-ja;迁移兼容性最好
bxjsarticlepdfLaTeX / XeLaTeX / LuaLaTeX /(u)pLaTeXbxjscls:跨引擎的薄共通层,也是 XeLaTeX 路线的入口

选文档类时总会被一件事吓一跳:同样的 10pt 并不代表同样的大小。用 jsarticle 以 10pt 排版,量一个汉字的宽度只有 9.24683pt——因为类文件里写着 \def\Cjascale{0.924690},把日文字体按西文的 0.924690 倍加载。换成 jlreq 做同样的事,一个汉字正好是 10pt。所以把 jsarticle 换成 jlreq,行长、每行字数和整个版心都会变。迁移时请当成重新排版,而不是同条件对比。

latex
% route 1: uplatex main.tex && dvipdfmx main.dvi
\documentclass[uplatex,dvipdfmx]{jsarticle}
\begin{document}
日本語と English を混ぜた段落です。約物(かっこ)も混じります。
\end{document}
latex
% route 2: lualatex main.tex
\documentclass{ltjsarticle}
\usepackage{luatexja-fontspec}
\setmainjfont{Harano Aji Mincho}
\setsansjfont{Harano Aji Gothic}
\begin{document}
日本語と English を混ぜた段落です。約物(かっこ)も混じります。
\end{document}

pLaTeX 路线上用不了的宏包:fontspec、unicode-math、microtype

速度的代价是:pLaTeX 路线把以 OpenType 为前提的现代宏包整批挡在门外\usepackage{fontspec} 会停在 ! Fatal Package fontspec Error: The fontspec package requires either XeTeX or(下一行是 LuaTeX),unicode-math 则回以 ! Package unicode-math Error: Cannot be run with uptex!。最麻烦的是 microtype:它不报错,只在日志里写下 Package microtype Warning: You don't seem to be using pdftex, luatex or xetex.,然后默默什么也不做——加载了却不起作用,是最难察觉的一种坏法。与此同时,graphicxhyperreftikz 只要驱动对上就照常工作,所以并不是"只能用老宏包"。界线正好画在 OpenType 与微观排版这一带。

PDF 书签乱码:hyperref 与 pxjahyper

在 (u)pLaTeX 下用 hyperref 写日文标题,只有 PDF 书签会乱码:正文完全正常,大纲面板却是一堆怪字。原因是 hyperref 把标题的 UTF-8 字节原样倒进了 UTF-16BE 字符串。打开生成的 .out 文件就能看到 \376\377\000\346\000\227\000\245…——每个字节后面都塞了一个空字节。加上 \usepackage{pxjahyper},同一个标题变成 \376\377\145\345\147\054\212\236…,也就是 0x65E5 0x672C 0x8A9E,正是「日本語」三个字。在 (u)pLaTeX 上用 hyperref,就把 pxjahyper 当成必需品。 在 LuaLaTeX + LuaTeX-ja 下,单用 hyperref 就能输出正确的 UTF-16BE,不需要这道手续。

latex
% (u)pLaTeX: load pxjahyper AFTER hyperref, or the outline panel is garbage
\documentclass[uplatex,dvipdfmx]{jsarticle}
\usepackage{hyperref}
\usepackage{pxjahyper}
\begin{document}
\section{日本語の見出し}
本文。
\end{document}

到底该选哪一条

若是新开的文档,请选 LuaLaTeX + LuaTeX-ja(ltjsarticlejlreq。用三分之一秒换来的是:Unicode 一路畅通、可用系统字体、fontspecunicode-mathmicrotype 都能工作,书签也自然正确。如果院系的学位论文模板或会议样式文件以 jsclasses 为前提,或者编译时间在密集修改中确实要紧,那就留在 upLaTeX + dvipdfmx——它不是遗留路线,而仍是最快的路线。选 XeLaTeX + zxjatype 的场合是:你已有 XeLaTeX 文档,需要往里加日文;单为日文而新选它的理由不多。至于普通 platex,新文档已没有理由用它;改用 uplatex 能排出的字符范围更广(这一差别见"文字编码与换行")。

  • 新文档、没有单位限制lualatex + ltjsarticle(或 jlreq);Unicode、系统字体、microtype 全都可用。
  • 既有模板以 jsclasses 为前提,或在意编译速度uplatex + dvipdfmxptex2pdf -l -u 可并成一条命令。
  • 已经有 XeLaTeX 文档bxjsarticle + zxjatype + zxjafont;注意 jlreq 在 XeLaTeX 下不能用。
  • 在 (u)pLaTeX 上用 hyperref,就一定要加载 pxjahyper "只有书签乱码"几乎都是这个原因。
  • microtype 在 (u)pLaTeX 上形同虚设——只警告、不生效。想要它就走 Lua 或 Xe 路线。