如果你收到过「第 12 页第三段第四行」这样的审稿意见,那么你已经知道 LaTeX 校对首先需要什么了:lineno 宏包提供的行号。许多期刊在投稿时就要求带行号的稿件,而只要页边有号码,一条意见只需一个词——「L412」。本页跟着走完一轮完整的校对流程:给行编号、在页边贴上待办、按作者给合作者的修改上色、用 latexdiff 把「自上次以来改了什么」直接做成 PDF 展示,再放出 chktex、lacheck、nag 三个检查器机械地挑毛病。下面每一步都附有实际运行的输出。
给审稿稿件加行号——lineno 宏包
载入 \usepackage{lineno} 并写上 \linenumbers,此后正文的每一行都会在左边距出现行号。选项可以改变呈现方式:modulo 只给每第五行编号,页边就不会太吵;pagewise 让计数在每一页从 1 重新开始;switch 在双面排版时把号码放到外侧页边。用 \nolinenumbers 可以关掉,只给一段范围编号则用 linenumbers 环境。只给文档的一部分编号——比如只给正文不给附录——是家常便饭的需求。
\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)这里有一个不知道就会让审稿对不上号的陷阱:默认情况下,行间公式不会被编号。 在一份含 amsmath 的 equation 和 align 的文档上实测,正文行号是 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 里。
# 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.3(latexdiff --version 自报「(c) 2004-2022 F J Tilmann」),它注入的导言区把 \DIFadd 定义为 {\protect\color{blue}\uwave{#1}},把 \DIFdel 定义为 {\protect\color{red}\sout{#1}}。也就是说:新增是蓝色波浪下划线,删除是红色删除线。这个默认样式有名字,叫 --type=UNDERLINE,可选的还有 CTRADITIONAL、TRADITIONAL、CFONT、FONTSTRIKE、INVISIBLE、CHANGEBAR、CCHANGEBAR、CULINECHBAR、CFONTCHBAR、BOLD、PDFCOMMENT。如果审稿人要黑白打印,FONTSTRIKE 或 BOLD 会好读得多。
% 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)。
\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」,实测中每一条都带着页码列了出来——还剩什么没做,一眼就看得见。
\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」定为固定步骤,会稳妥得多。
chktex 与 lacheck——捡起 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,则会两次冒出这条警告:
$ 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)只盯着 chktexrc 中 Linker 列表里的项——\ref、\vref、\pageref、\eqref、\cite——之前的位置,像 Fig. 1 这样的裸字符串不在范围内。想让它抓住 Fig.,得自己把它加进 Abbrev 列表。加好之后再检查同一个文件,这回 Warning 12 in ref.tex line 3: Interword spacing should perhaps be used. 就指向了 Fig. 的位置——指出这个句点后面会被塞进句末的宽空白。
| 警告号 | 指出的问题 | 触发它的写法 |
|---|---|---|
2 | Non-breaking space (~) should have been used. | Table \ref{...} 或 see \cite{...} 前的普通空格 |
8 | Wrong length of dash may have been used. | 表示范围或破折号却只用了一个 - |
11 | You should use \ldots to achieve an ellipsis. | 直接敲 ... 而不用省略号命令 |
12 | Interword spacing should perhaps be used. | Abbrev 中登记的缩写的句点之后 |
18 | Use TeX quotes as an alternative to the straight double quote. | 使用了直立双引号 " |
26 | You ought to remove spaces in front of punctuation. | 标点前留了空格 |
29 | $\times$ may look prettier here. | 像 5x10^3 那样拿 x 当乘号 |
9 | 'itemize' expected, found 'enumerate'. | 环境嵌套对不上 |
# 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 更快带你到答案。正确做法是两个都跑。
$ 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.texchktex 的退出状态就是警告的条数,这一点在接入 CI 时很管用。上面的实测里,chktex -q ref.tex 的退出码是 2(两条警告),用 -n2 静音第 2 号警告后变成 0。也就是说,「唯独容忍这一项检查」的方针可以写成仓库配置。逐项目的设置用 -l my.chktexrc 载入,或者用 % chktex-file 26 这类源内指令局部抑制。
斥责过时命令与未使用的标签——nag 与 refcheck
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. instead、Command \centerline is TeX. Use \centering or center environment instead、Package 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.。编了号却从未被引用的公式,通常正是「不该编号的公式」;被要求压缩篇幅时,那也是最先能删的地方。
\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;能交给机器的挑错则交给 chktex、lacheck、nag。这四者并不冲突。按下面的顺序走一遍,就不必对同一份稿子重复劳动。
- 写作期间用
todonotes的\todo{...}与\missingfigure{...}留下待办,并用\listoftodos数还剩多少。 - 传给合作者之前先跑
chktex、lacheck、nag,把机器能挑出的毛病先清掉,不要把人的注意力花在这些上面。 - 送稿时编译一份启用了
lineno与\linenumbers的版本;公式多的话再加上mathlines。 - 收回来的批注用
changes的\added/\replaced/\deleted承接,再用\listofchanges防止漏处理。 - 每交出一版,就同时附上
latexdiff --flatten old.tex new.tex > diff.tex编出的差分 PDF,对方只看这一份即可。 - 投稿前用
refcheck清理未使用标签,再把todonotes切到disable、changes切到final重新编译。