校对 (proofread)

如果你收到过「第 12 页第三段第四行」这样的审稿意见,那么你已经知道 LaTeX 校对首先需要什么了:lineno 宏包提供的行号。许多期刊在投稿时就要求带行号的稿件,而只要页边有号码,一条意见只需一个词——「L412」。本页跟着走完一轮完整的校对流程:给行编号、在页边贴上待办、按作者给合作者的修改上色、latexdiff 把「自上次以来改了什么」直接做成 PDF 展示,再放出 chktexlachecknag 三个检查器机械地挑毛病。下面每一步都附有实际运行的输出。

给审稿稿件加行号——lineno 宏包

载入 \usepackage{lineno} 并写上 \linenumbers,此后正文的每一行都会在左边距出现行号。选项可以改变呈现方式:modulo 只给每第五行编号,页边就不会太吵;pagewise 让计数在每一页从 1 重新开始;switch 在双面排版时把号码放到外侧页边。用 \nolinenumbers 可以关掉,只给一段范围编号则用 linenumbers 环境。只给文档的一部分编号——比如只给正文不给附录——是家常便饭的需求。

latex
\usepackage[modulo]{lineno}   % number every 5th line only
% \usepackage[pagewise]{lineno} % restart the count on each page
% \usepackage[switch]{lineno}   % outer margin, for twoside
\linenumbers
...
\nolinenumbers                % stop numbering (e.g. before the appendix)

这里有一个不知道就会让审稿对不上号的陷阱:默认情况下,行间公式不会被编号。 在一份含 amsmathequationalign 的文档上实测,正文行号是 1、2、3,直接跨过了公式。给同一份文档加上 \usepackage[mathlines]{lineno} 后,编号从 1 一路排到 6,公式的每一行也都有号。也就是说,加不加 mathlines,会改变「L4」指向哪里;若审稿人和作者看的是不同设置编译出的 PDF,讨论就对不上。公式多的稿件,打开 mathlines 更保险。

选项或命令效果
\linenumbers从此处开始给正文编行号
\nolinenumbers停止编号(例如放在附录之前)
modulo只在每第五行打印号码
pagewise每页从 1 重新计数
switch双面排版时把号码放到外侧页边
mathlines也给行间公式的每一行编号(默认不编)

lineno 是一件从 1995 年延续至今的老工具,TeX Live 2024 里收录的是 2023 年 5 月 20 日的 v5.3。宏包开头的版权声明并列着三代维护者:1995–2003 年是 Stephan I. Böttcher,4.x 系列是 Uwe Lück,5.x 系列则由 Karl Wette 从 2021 年接手。经历了三十年的兼容性周旋,它与其他宏包搭配时的行为并不总是那么听话。载入顺序有时会影响结果,所以号码怎么都出不来时,不妨把 lineno 放到最后载入。

latexdiff 怎么用——把「哪里变了」直接做成 PDF 给合作者看

执行 latexdiff old.tex new.tex > diff.tex,再把生成的 diff.tex 照常交给 pdflatex 编译,就这么简单,得到的 PDF 里新增与删除的文字在视觉上一目了然。实际跑一遍会看到,latexdiff 在原文中穿插了 \DIFaddbegin\DIFadd{...}\DIFdelbegin\DIFdel{...} 这些标记,并在导言区补上 \RequirePackage[normalem]{ulem} 和颜色定义。它之所以在整套校对工具里价值最高,是因为它让对方不必去读 LaTeX:差异呈现在排好版的页面上,而不是 git 的 diff 里。

terminal
# the whole workflow
latexdiff old.tex new.tex > diff.tex
pdflatex diff.tex

# one file made of many \input files: flatten first
latexdiff --flatten old/main.tex new/main.tex > diff.tex

# diff straight against a git revision (produces main-diffHEAD~1.tex)
latexdiff-vc --git --flatten -r HEAD~1 main.tex

# a different visual style, e.g. bold instead of underline
latexdiff --type=CFONT old.tex new.tex > diff.tex

默认的外观,比常说的「新增加下划线、删除加删除线」要更具体一些。TeX Live 2024 收录的是 v1.3.3latexdiff --version 自报「(c) 2004-2022 F J Tilmann」),它注入的导言区把 \DIFadd 定义为 {\protect\color{blue}\uwave{#1}},把 \DIFdel 定义为 {\protect\color{red}\sout{#1}}。也就是说:新增是蓝色波浪下划线,删除是红色删除线。这个默认样式有名字,叫 --type=UNDERLINE,可选的还有 CTRADITIONALTRADITIONALCFONTFONTSTRIKEINVISIBLECHANGEBARCCHANGEBARCULINECHBARCFONTCHBARBOLDPDFCOMMENT。如果审稿人要黑白打印,FONTSTRIKEBOLD 会好读得多。

latex
% What latexdiff actually writes into diff.tex (excerpt of a real run):
\RequirePackage[normalem]{ulem}
\providecommand{\DIFadd}[1]{{\protect\color{blue}\uwave{#1}}}
\providecommand{\DIFdel}[1]{{\protect\color{red}\sout{#1}}}
...
The measured value was \DIFdelbegin \DIFdel{3.2}\DIFdelend
  \DIFaddbegin \DIFadd{3.4}\DIFaddend \,mm.

它在实务中主要有两处派上用场。一是投稿的修改稿——许多期刊要求在干净稿之外同时提交一份「标出修改」的版本,而 latexdiff 的输出本身就是这份交付物。二是发给合作者。文档若分成多个文件,加上 --flatten 就会先展开 \input 再比较;latexdiff-vc --git --flatten -r HEAD~1 main.tex 则直接把工作区和某次历史提交对比,写出 main-diffHEAD~1.tex。要注意的是,latexdiff 把 LaTeX 当作文本而非语法来处理,因此表格和图内部的改动可能把标记弄坏。真坏了,就用 --append-safecmd 把相关命令加入安全表,或用 --exclude-textcmd 排除该环境。

按作者分色标注——changes 宏包

latexdiff从外部比较两个版本,changes 则把修改写进源文件内部。先注册作者,如 \definechangesauthor[name={Ada Lovelace}, color=blue]{AL},然后用 \added[id=AL]{...}\deleted[id=AL]{...}\replaced[id=AT]{新}{旧}\comment[id=AT]{...} 做批注。放上 \listofchanges 会生成一份「List of changes」,实测中依次列出了 Added (AL): ...Replaced (AT): ...Deleted (AL): ...Commented (AT): ...,并标明各自出自谁手。TeX Live 2024 收录的是 v4.2.1(2021/07/15)

latex
\usepackage[markup=underlined]{changes}   % draft look; the default
\definechangesauthor[name={Ada Lovelace}, color=blue]{AL}
\definechangesauthor[name={Alan Turing},  color=orange]{AT}
\begin{document}
\listofchanges
The engine \added[id=AL]{weaves algebraical patterns} and
\replaced[id=AT]{computes}{calculates} the numbers.
\deleted[id=AL]{This sentence is redundant.}
\comment[id=AT]{Check the citation here.}

收尾的这一手很关键。切换到 \usepackage[final]{changes} 之后,宏包并不是把标记藏起来,而是把编辑真正应用上去。实测中,\added 的文字保留下来,\replaced 只留下新的一侧,\deleted 的那句和 \comment 都消失了,得到干净的成稿:「The engine weaves algebraical patterns and computes the numbers. Ordinary text.」也就是说,制作最终版时不必逐条手工删命令。反过来说,切换前后的 PDF 内容并不相同,所以投稿前务必用 final 重新编译并通读一遍。

把待办贴在页边——todonotes\todo

% 写的注释一编译就没了——所以合作者根本看不到。todonotes 就是为了让待办留在输出里而存在的。\todo{check this number} 会在页边画一张便签,\todo[inline]{...} 在正文流中放一条横带,\missingfigure{plot of the residuals} 则画一个「这里要放图」的占位方框。\listoftodos 会在开头生成一份「Todo list」,实测中每一条都带着页码列了出来——还剩什么没做,一眼就看得见。

latex
\usepackage[textwidth=3cm]{todonotes}
% \usepackage[disable]{todonotes}   % final version: hides notes AND the list
\begin{document}
\listoftodos
Some text.\todo{check this number}
More text.\todo[inline,color=green!30]{rewrite this paragraph}
\missingfigure{plot of the residuals}

做成品版时换成 \usepackage[disable]{todonotes}。实测下来,页边便签、\missingfigure 的方框,连同开头的「Todo list」都一并消失了——而命令仍旧留在源文件里,下次修订时去掉 disable,所有待办又会回来。这正是与 % 注释的决定性差别:待办是被隐藏而不是被销毁。风险在于带着 disable 直接投稿,结果没人发现还有未处理项。把「投稿前先关掉 disable,看一遍 \listoftodos」定为固定步骤,会稳妥得多。

chktexlacheck——捡起 pdflatex 只字不提的错误

这两者捡的是排版上说得通、对读者却不友好的错误,因为 pdflatex 只关心编译能否通过。举个实例:把 As shown in Fig. 1 and in Table \ref{tab:one}, see also \cite{knuth1984}. 这一行交给 pdflatex,回来的只有 LaTeX Warning: Reference 'tab:one' on page 1 undefined 这样一条关于交叉引用的提示。同一行交给 chktex,则会两次冒出这条警告:

terminal
$ chktex -q ref.tex
Warning 2 in ref.tex line 3: Non-breaking space (`~') should have been used.
As shown in Fig. 1 and in Table \ref{tab:one}, see also \cite{knuth1984}.
                               ^
Warning 2 in ref.tex line 3: Non-breaking space (`~') should have been used.
As shown in Fig. 1 and in Table \ref{tab:one}, see also \cite{knuth1984}.
                                                       ^

这里要更正一点。常有人说「chktex 会揪出 Fig. 1 少了 ~」,在默认配置下它并不会chktex~ 检查(警告 2)只盯着 chktexrcLinker 列表里的项——\ref\vref\pageref\eqref\cite——之前的位置,像 Fig. 1 这样的裸字符串不在范围内。想让它抓住 Fig.,得自己把它加进 Abbrev 列表。加好之后再检查同一个文件,这回 Warning 12 in ref.tex line 3: Interword spacing should perhaps be used. 就指向了 Fig. 的位置——指出这个句点后面会被塞进句末的宽空白。

警告号指出的问题触发它的写法
2Non-breaking space (~) should have been used.Table \ref{...}see \cite{...} 前的普通空格
8Wrong length of dash may have been used.表示范围或破折号却只用了一个 -
11You should use \ldots to achieve an ellipsis.直接敲 ... 而不用省略号命令
12Interword spacing should perhaps be used.Abbrev 中登记的缩写的句点之后
18Use TeX quotes as an alternative to the straight double quote.使用了直立双引号 "
26You ought to remove spaces in front of punctuation.标点前留了空格
29$\times$ may look prettier here.5x10^3 那样拿 x 当乘号
9'itemize' expected, found 'enumerate'.环境嵌套对不上
terminal
# The messages exactly as chktex prints them (one run, one bad file):
Warning 18 in bad.tex line 4: Use either `` or '' as an alternative to `"'.
Warning 11 in bad.tex line 4: You should use \ldots to achieve an ellipsis.
Warning 26 in bad.tex line 4: You ought to remove spaces in front of punctuation.
Warning 29 in bad.tex line 5: $\times$ may look prettier here.
Warning  8 in bad.tex line 7: Wrong length of dash may have been used.
Warning  9 in mm.tex  line 5: `itemize' expected, found `enumerate'.

lacheck 要沉默得多,职责也因此更清楚。chktex 用正则表达式盯写作习惯,lacheck 则检查文档的结构一致性。给它一份用 \end{enumerate} 去闭合 \begin{itemize} 的文件,它会成对地指出两处位置——"mm.tex", line 5: <- unmatched "\end{enumerate}""mm.tex", line 3: -> unmatched "\begin{itemize}"。同样的错误 chktex 也会以 Warning 9 捡起来,但只指向闭合的那一处。在长文档里,若问题是「嵌套从哪里开始出错」,lacheck 更快带你到答案。正确做法是两个都跑。

terminal
$ lacheck mm.tex
"mm.tex", line 5: <- unmatched "\end{enumerate}"
"mm.tex", line 3: -> unmatched "\begin{itemize}"

# chktex exits with the number of warnings, so CI can gate on it
$ chktex -q ref.tex >/dev/null; echo $?
2
# mute one check by number and it passes
$ chktex -q -n2 ref.tex >/dev/null; echo $?
0
# teach it your abbreviations via a local rc file
$ printf 'Abbrev { Fig. Eq. Sec. }\n' > my.chktexrc
$ chktex -q -l my.chktexrc ref.tex

chktex 的退出状态就是警告的条数,这一点在接入 CI 时很管用。上面的实测里,chktex -q ref.tex 的退出码是 2(两条警告),用 -n2 静音第 2 号警告后变成 0。也就是说,「唯独容忍这一项检查」的方针可以写成仓库配置。逐项目的设置用 -l my.chktexrc 载入,或者用 % chktex-file 26 这类源内指令局部抑制。

斥责过时命令与未使用的标签——nagrefcheck

nag 是在编译时指出「还能跑但已经过时」写法的宏包。把 \RequirePackage[l2tabu,orthodox]{nag} 放在 \documentclass 之前再编译,日志里就会排出一串抱怨——Package nag Warning: Command \bf is an old LaTeX 2.09 command. 后接 Use \bfseries or \textbf instead on input line 5.,接着是 $$...$$ is obsolete. Use \[...\] et al. insteadCommand \centerline is TeX. Use \centering or center environment insteadPackage epsf is obsolete. Use the graphicx package instead.,最后以 Package nag Warning: 5 complaints in total. 收尾。接手一份基于几十年前模板的稿件时,第一遍就跑它,收益最大。

还有一件值得在投稿前跑一次的工具是 refcheck(TeX Live 2024 收录 v1.9.1)。加上 \usepackage{refcheck} 编译两遍,它就会把无人引用的标签列进日志——实测中出现了 Package refcheck Warning: Unused label 'sec:unused' on input line 5.Package refcheck Warning: Unused label 'eq:never' on input line 7.。编了号却从未被引用的公式,通常正是「不该编号的公式」;被要求压缩篇幅时,那也是最先能删的地方。

latex
\RequirePackage[l2tabu,orthodox]{nag}   % MUST come before \documentclass
\documentclass{article}
\usepackage{refcheck}                   % lists labels nobody refers to
% Then: pdflatex paper.tex && grep -E "nag Warning|refcheck Warning" paper.log

一轮校对该怎么走

该拿哪件工具,取决于给谁看什么。只需你和合作者看到的待办用 todonotes;需要留下「谁改的」用 changes;向审稿人展示「相对上一版改了什么」用 latexdiff;能交给机器的挑错则交给 chktexlachecknag。这四者并不冲突。按下面的顺序走一遍,就不必对同一份稿子重复劳动。

  • 写作期间用 todonotes\todo{...}\missingfigure{...} 留下待办,并用 \listoftodos 数还剩多少。
  • 传给合作者之前先跑 chktexlachecknag,把机器能挑出的毛病先清掉,不要把人的注意力花在这些上面。
  • 送稿时编译一份启用了 lineno\linenumbers 的版本;公式多的话再加上 mathlines
  • 收回来的批注用 changes\added / \replaced / \deleted 承接,再用 \listofchanges 防止漏处理。
  • 每交出一版,就同时附上 latexdiff --flatten old.tex new.tex > diff.tex 编出的差分 PDF,对方只看这一份即可。
  • 投稿前用 refcheck 清理未使用标签,再把 todonotes 切到 disablechanges 切到 final 重新编译。