XF

LaTeX: Concepts for Beginners

Concepts

This section is based on LaTeX vs. MiKTeX: The levels of TeX and The TeX FAQ.

“TeX” 是一个庞大的世界,而 “TeX” 这个词在日常使用时非常容易造成混淆,让人(尤其是新手)迷惑。在有了一点(不明就里地)使用 LaTeX 写作论文的经验后,我们有必要回过头来理清一些 TeX/LaTeX 相关概念。

1. What is TeX and what is LaTeX?

在上面短短的一段文字中,我就使用了 “TeX” 和 “LaTeX” 两个不同的名词,的确令人迷惑。简而言之,“TeX” 是一个 Donald E. Knuth 发明的排版系统(typesetting system),类似于一种编程语言,可以靠写代码来生成书籍、文档。但是,非计算机/排版专业人士写起这种语言来可能会感觉非常头痛:我只是想写 “第一章”、“第二节”、“强调”、“加粗”,为什么要我去写包含了大量 \if ... \fi ... \let 的乱七八糟的符号呢(就像写 Python、C++ 那样)?有没有更方便的表达方式呢?于是有了 “LaTeX”。

“LaTeX” 是一个 “TeX” 的宏集(macro package),它是用 “TeX” 这种编程语言写成的抽象出了很多实用的命令,给普通的论文写作者更便捷的表达排版需求的方式。例如,有了 “LaTeX”,我们可以写出下面这样的代码:

\documentclass{article}
\title{This is my awesome title!}
\author{Me}

\begin{document}
\maketitle
Hello, World!
\end{document}

这看上去非常自然:仅仅几句命令就定义了文档类型、标题、作者,还有文档正文的环境。这些简单的命令背后都是用(对非专业人士而言非常复杂、不直观的)“TeX” 代码实现的。(点这里可以看 \begin{document} 这条命令背后到底使用了什么样的 “TeX” 代码。)

由于普通的论文写作者几乎不会直接写 “TeX” 代码(对于直接使用 “TeX” 编写的文档,我们通常称之为 “plain TeX” 文件,以示与 “LaTeX” 的区分), 大家平时都是用 “LaTeX” 提供的那些方便的命令来写作,因此,我们在本文剩余的部分将只讨论 LaTeX。

2. “我用 LaTeX 写论文” 这句话什么意思?

当我说 “我用 LaTeX 写论文”,我指的是:

  1. 我创建了一个 xxx.tex 文件,在里面打了一些字。我打的这些字必须符合 LaTeX 的格式要求(这里的 “LaTeX” 指的是 LaTeX 语言规范和风格);
  2. 我使用了一套(可能不止 1 个)工具,将这个文件编译(转换)生成了一个 xxx.pdf 文件。这套工具我们也笼统地称之为 “LaTeX”。

所以,当我们平时说 “LaTeX”,我们可能在笼统地指称几个不同层面的东西(语言格式、软件)。

3. Engines 引擎

上文提到,我们“用 LaTeX 写论文”,其实是做了这样的转换:

         some tool(s)
xxx.tex =============> xxx.pdf

这里的 “some tool” 必定是一个可以在电脑上运行的软件。这个 tool 就是 LaTeX Engine。

由于我们已经有了一套规则(即 LaTeX 语法规则)来决定某个命令显示在 PDF 里是什么样子,而 LaTeX Engine,把这个转换的过程真正执行了,我们可以说 LaTeX Engine “实现(Implements)” 了 LaTeX。

具体的 LaTeX Engine 有很多种(毕竟已有一套语法规则,如果你有较强编程能力,你自己也可以写一个能 “实现” 这套规则的转换软件,即自己的 LaTeX Engine),常见的有 LaTeX(默认输出 .dvi 文件)、pdfLaTeX、XeLaTeX。

不同的 Engines 有一些功能上的不同,例如,“LaTeX” 引擎会把 .tex 源码转换成 .dvi 格式,而 pdfLaTeX 会把 .tex 源码直接输出成 .pdf 格式。而 XeLaTeX 不仅可以直接输出 .pdf 文件,且支持 UTF-8 编码,可以更方便地编译中文文档。我们平时使用较多的引擎是 pdfLaTeX 和 XeLaTeX。

当我完整地叫出 “pdfLaTeX”、“XeLaTeX” 这样的名词的时候,我一定指的是 Engine。

4. Packages 包/宏集

刚才我们实际上已经用过 “macro package” 这个词了,我们说 “LaTeX 是 TeX 的一个宏集”。但是,LaTeX 宏集比较基础,它不是万能的,文档写作者们经常需要一些方便的个性化命令,例如,我们想要 “创建一个三线表”,这个表的三条线(粗细略有不同)应该怎么打出来呢?当然可以设法自己使用 LaTeX 基础的命令去画,但更好的办法是直接使用 booktabs 这个 “Package”,这里面提供了三条新的命令(宏,macro):\toprule\midrule\bottomrule,分别生成三线表里用到的那三条线。

\usepackage{booktabs}

% ...
\toprule
\midrule
\bottomrule
% ...

又比如,我们想更方便地调整文章的页面边距等参数,可以使用 geometry Package,里面提供了 margin 等参数设置。又如,我们想在 LaTeX 文档里插入可以跨页的表格, 可以使用 longtable;想控制浮动体(floats,例如图片和表格)的位置(尽量在页面上方,下方,还是强制在某个位置),可以使用 float Package。

总而言之,Packages 系统提供了对 LaTeX 的扩展功能。TeX Live(下文细讲)里面包含了上千个 Packges,是由来自世界各地的人创建的。你也可以自己写一个自己的 Package,以后自己用,或者发布到网上让别人用。

5. Front ends 前端 and editors 编辑器

在此我们不严格区分这两个词,下文我将统一称作 “编辑器”(editor)。当然,你在安装 TeX Live 的时候见到了 “Install front end” 的选项,看完本节就明白那是什么了。

重新回顾我们刚才对 “我用 LaTeX 写论文” 这句话的阐述。前文写到:

我创建了一个 xxx.tex 文件,在里面打了一些字。…

别看这个文件是 .tex 结尾的,它本质上就是一个 .txt 纯文本文件,叫它 .tex 是为了其他人和辅助软件能更方便地“意识到”它的内容是符合 LaTeX 语法规范的。类似地,.py 文件本质上也是 .txt,叫它 .py 是为了让其他人和辅助软件意识到这个文件里面的内容是 Python 代码。

那么想想,如果你平时创建了一个文本文件,想在里面打字,你会怎么做呢?右键这个文件使用 Windows 记事本打开(如果它真的是 .txt 结尾的话,甚至在 Windows 里直接双击就能自动用记事本打开),然后用键盘在里面敲字母?Bingo!这是一种正确的方法。Windows 记事本就是一种“编辑器”。

当然,这不是最好的,因为 Windows 记事本的功能太简陋了。整个界面是黑白的,也没有任何拼写检查、自动提示的功能,代码写错了或者命令打错了很难用肉眼看出来。所以,人们常常喜欢使用其他的更加“智能”的编辑器,例如,VS Code。它有很多语法高亮功能,可以用不同颜色(当然,Keep in mind:这些颜色是 VS Code 临时显示出来的,并不是 .tex 代码有颜色。.txt 等纯文本文件没有字体、颜色的概念)表示各种命令,可以检查括号是不是正确配对了等。

上述记事本、VS Code,还有 Sublime Text 等,不局限于特定的语言,你可以拿它们来写小说,可以编辑 .py 文件写 Python 代码,可以编辑 .tex 文件写 LaTeX 等等。它们可以称为“通用文本编辑器”。还有一类编辑器,例如 TeXworks,是专门为写 LaTeX 设计,它的界面上有一键编译的按钮(注意,这不意味着 TeXworks 这个软件可以编译 LaTeX 文档,而是它可以一键帮用户调用 LaTeX Engines 编译这个文档。如果你电脑上只安装了 TeXworks 软件却没有安装任何 LaTeX 引擎,显然是行不通的)。如果在安装 TeX Live 时选择了安装 front end,那么你的电脑就会被安装上 TeXworks 这个软件。

我个人觉得 VS Code 配合一些特定插件(LaTeX WorkShop)编辑 .tex 源码非常好用,体验超越 TeXworks 这种专门的 LaTeX 编辑器(前端),所以,我平时是尽可能不安装 TeXworks 这样的用不到的前端软件的。

6. Distributions 发行版 and Package manager 包管理器

刚才已经多次提到 TeX Live,如果你注意观察的话,可能已经猜到了——TeX Live 里面有各种 LaTeX Engines,有上千个 Packges,有(可以选择不安装的)TeXworks 编辑器、有一些字体、有这个、有那个——TeX Live 就是上面提到的几乎所有 TeX 相关的东西的一个集合。这个集合就称为一个“发行版(distribution)”。这个发行版通常可以在网上下载,然后安装。当然,发行版也有很多种,目前最常见的是 TeX Live 和 MikTeX。我们现在使用前者。

当一个人说“我想在我电脑上安装 LaTeX”的时候,他通常指的是“安装某个 TeX 发行版,例如 TeX Live”。

刚才说到,一个发行版里可能包含上千个 Packages,这些 Packages 可能是由世界各地的作者编写的。这些 Packages 可能会时不时地更新、增加功能。如果有一个 Package 更新了,添加了一个功能正好是我需要的,但我电脑上的 TeX Live 是在这个 Package 更新之前安装的,我想用这个 Package 的更新版本,怎么办呢?TeX Live 发行版提供了一个包管理器(package manager),叫做 tlmgrTeX Live package manager),我们可以用它来更新特定的 Package 或者所有的 Package,就像在 iOS App Store 里更新 App 里一样。另外,TeX Live 发行版作为一个“整体”每年会更新一次,所以我们会下载到 “TeX Live 2022” 这样的安装包。明年春夏季再下载可能就会是 TeX Live 2023 了。

你可能会疑问:“为什么你总说 TeX Live 有好几个 GB 大小,但是我在这里 下载的安装程序只有 20MB 左右?”那是因为,这是一个“在线安装程序”,它本体只有 20MB 左右,但是在安装的过程中会下载几千个包还有各种 LaTeX engines 等资源。当然,你也可以选择直接先下载一个好几 GB 的 DVD 安装盘镜像,然后离线安装

7. CTAN and Mirrors

TeX 的世界里有那么多的东西:有各种引擎、几千个 Packages、别人打包好的发行版……我可以用 tlmgr 更新 Packages;那个在线安装器还能一边下载一边安装:是不是有一个网站,它上面存放了所有的这些 TeX 相关的资源,我们下载安装发行版、更新 Packages 等,都是从这个网站下载的?

答案是肯定的。当然这个“网站”并不是孤零零一个网站,而是一个“网络(Network)”,名叫 CTAN(The Comprehensive TeX Archive Network)。考虑到 TeX 的世界里有那么多东西,如果只有一个服务器,全世界的人都从这个服务器下载,那么它的负担太重。所以,CTAN 在全世界各地都有镜像站(Mirrors)。所谓镜像站,就是比如美国有一个服务器上面存放了 TeX 的相关资源,我一模一样 copy 一份放在一台中国服务器上,中国的 TeX 用户直接从这个服务器下载,离得近、网速快,还节省了去往美国服务器的国际带宽,岂不美哉?

清华大学 TUNA 协会就运营了这样一个镜像站中科大也有。中国的很多高校和大公司(如阿里巴巴、腾讯)都有自己运营的镜像站。

8. Overleaf and Dropbox and GitHub

前面基本介绍完了在自己的电脑上写 LaTeX 文档涉及到的概念,但有人问:“在自己电脑上安装配置 LaTeX 的环境太麻烦了,再说如果我和另一个人合作写论文,.tex 源码发来发去也很麻烦(虽然很多 Word 使用者也是这么干的),有没有什么方便的方案满足我下面的两个诉求?

  1. 不用在自己电脑上安装 LaTeX (我的电脑空间很小,性能很差,我不想再装大型软件啦!);
  2. 像 Google Docs 云端文档那样,可以好几个人一起协作编辑。”

Overleaf 就是这样的一个网站。它把 TeX Live 搬到了服务器上,使用户可以直接在网页浏览器中写 LaTeX 代码,并可以点击编译,生成 PDF。

如果你有 Overleaf Premium 帐号,还可以将 Overleaf 的文档同步至 Dropbox、GitHub。 以 Dropbox 为例,配置好 Overleaf 与 Dropbox 的连接后,你的 Dropbox 中 Apps/ 目录下会出现一个 Overleaf/ 文件夹,里面是你在 Overleaf 中的文档。如果你在自己的电脑上也安装了 Dropbox 软件,假设你电脑上 Dropbox 文件夹的路径是 %USERPROFILE%\Dropbox,那么,如果你进入 %USERPROFILE%\Dropbox\Apps\Overleaf\SOME-ARTICLE 文件夹,就可以用自己电脑上安装的编辑器去编辑 SOME-ARTICLE 中的 LaTeX 源码了。当然也可以在自己电脑上使用 LaTeX 引擎去编译、看效果;在自己电脑上所做的修改,在网络通畅、Dropbox 软件保持运行的状态下,几秒钟后也会同步到 Overleaf 上对应的地方。这样就实现了在本地、云端随心所欲编辑了。

Excercises 练习题

以下语境中的 “LaTeX” 分别指的是什么?如有多种含义,请尽量全面阐述。如果你认为某个 “LaTeX” 有多种含义,你能指出一种更 Specific 的表述方法吗?

  1. “我用 LaTeX 写论文。”
  2. “我在写 LaTeX。”
  3. “LaTeX 报错了。”
  4. “我要在我的电脑上安装 LaTeX。”
  5. “我用 LaTeX 基于 a.tex 文件生成了 a.pdf 文件。”

另请在 Overleaf 上观察探索:

  1. 你在 Overleaf 中使用的 TeX Live 是哪个版本?
  2. 你在 Overleaf 中使用哪个 LaTeX Engine?
  3. 你在 Overleaf 中编辑代码的那个区域,属于前述 1-8 小节中的哪个概念?
  4. 你是 Overleaf Premium 用户吗?
  5. 你的 Overleaf 账户与 Dropbox 连接了吗?如果未连接,应如何连接?如已连接,Dropbox 中对应的存放 Overleaf Project 的文件夹在什么位置?观察一下这个文件夹中的内容,与你在 Overleaf 网站上看到的自己的 Projects 一致吗?

在下文中,我可能会继续相对随意地使用 “LaTeX” 这个词,每当你看到这个词的时候,不妨思考一下:“LaTeX” 在此处指的是什么?是指语言规范?笼统地指整套基于 TeX 的排版系统?某个引擎?某个发行版?或是其他的什么层面?

Compiling

1. Basics

在上一节中,我留了一个小坑,即我在讲“我用 LaTeX 写论文” 时:

  1. 我使用了一套(可能不止 1 个)工具…

另,在 LaTeX Engines 部分:

         some tool(s)
xxx.tex =============> xxx.pdf

刻意标注了复数。这是因为,LaTeX 从 xxx.texxxx.pdf 的转换并不一定能“一趟”完成。假如我们有如下代码,文件名为 a.tex

\documentclass{article}

\begin{document}
\section{Section 1}
We'll talk more in Section \ref{sec:rand}.
\section{Section 2}\label{sec:rand}
Blahblah...
\end{document}

预期的效果应该是这样:

但实际上,如果在电脑命令行中执行(以下以 XeLaTeX xelatex 引擎为例,如有兴趣你也可以尝试 latexpdflatex

xelatex a.tex

的确在相同文件夹内会产生一个 a.pdf 文件,但是这样的:

即,在这次编译中,LaTeX 并没有正确地将文内的交叉引用(Crossref),即 \ref{sec:rand} 对应的标签 label{sec:rand} 解析成正确的数字编号。

其实,从人工的角度想想也合理:如果让你人工地看一遍 .tex 源码,只能从头到尾按顺序看,不许回头,你能正确地认出来文中的 Crossref 应该解析成什么吗?这无法实现,因为:

  1. 当你从上到下阅读源码,看到 ref{sec:rand} 的时候,你知道这里应该是要填入被引用的那个对象的编号。但是截至目前,我根本没见过 {sec:rand} 这个标签定义在哪儿,还没定义(undefined)你上来就直接引用它,我怎么知道它的编号是什么呢?所以我只能打上问号(??)了。
  2. 当你继续往下看,看到了 \section{Section 2}\label{sec:rand} 的时候,你恍然大悟:原来这个 \label{sec:rand} 是定义在这个 Section 旁边的。算起来,这是我见过的编号为 2 的 Section,那么,如果文中其他地方引用了这个标签 {sec:rand},我就替换成数字 “2” 好了。
  3. 可惜,由于从上到下不能回头,你已经没机会把之前的问号(??)改成正确的 “2” 了。

其实在 LaTeX 引擎的 Log 中,也可以看到这一点:

This is XeTeX, Version 3.141592653-2.6-0.999994 (TeX Live 2022) (preloaded format=xelatex)
 restricted \write18 enabled.
entering extended mode
(./a.tex
LaTeX2e <2022-11-01>
L3 programming layer <2022-11-02>
(c:/texlive/2022/texmf-dist/tex/latex/base/article.cls
Document Class: article 2022/07/02 v1.4n Standard LaTeX document class
(c:/texlive/2022/texmf-dist/tex/latex/base/size10.clo))
(c:/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-xetex.def)
No file a.aux.
(c:/texlive/2022/texmf-dist/tex/latex/base/ts1cmr.fd)

LaTeX Warning: Reference `sec:rand' on page 1 undefined on input line 5.

[1] (./a.aux)

LaTeX Warning: There were undefined references.


LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.

 )
Output written on a.pdf (1 page).
Transcript written on a.log.

LaTeX 面对这类情况是怎么做的呢?那就是多次运行(没错,一模一样的两条命令运行了两遍。上面的 Log 提示:Label(s) may have changed. Rerun to get cross-references right.):

xelatex a.tex
xelatex a.tex
  1. 第一趟,LaTeX 先扫描了一遍源码,记下了所有 \label{...} 的定义位置(也就知道了未来引用这些 labels 的时候,分别应该解析成什么编号),并保存在一个叫做 a.aux 的中间辅助文件(auxiliary file)中。
  2. 第二趟,LaTeX 同时读取 a.texa.aux 文件,这样当它在 a.tex 中遇到 ref{...} 命令引用某个标签时,马上就可以去 a.aux 文件中查询刚才记下的,这个被引用的标签应该被解析成什么信息(编号)。

如此两趟之后,正确解析了文内交叉引用的 PDF 文档就生成了。

2. With bibtex

上面的例子只使用了文内的 Crossref,但没有涉及参考文献的引用(citation)。如果你使用 .bib 文件管理参考文献数据库,在 a.tex 中使用 \cite{xxx} 命令引用文献,情况会更加复杂一些,一般而言,完整地解析所有的引用需要运行 4 步,其中 3 次 LaTeX 引擎 1 次 BibTeX:

xelatex a.tex
bibtex  a.tex
xelatex a.tex
xelatex a.tex

这个问答 解释了四趟运行分别做的工作:

  1. 第一步,运行前,我们只有一个 a.tex 文件,尚没有任何 .aux 等辅助文件。LaTeX 扫描一遍 a.tex,将其中涉及到的所有 \label 定义和 \cite{...} 中的 citation key 记录到 a.aux 辅助文件;
  2. 第二步,BibTeX 读取 a.aux 文件,发现里面有一些参考文献 citation keys。于是 BibTeX 去查询 .bib 文件(即你的参考文献数据库),将这些 citation keys 对应的参考文献的完整信息写入 a.bbl 文件;
  3. 第三步,LaTeX Engine 再次运行。与没有 BibTeX 的情况一样,它这次也会解决文内 Crossref 的问题;同时,它将 .bbl 文件中参考文献列表的完整信息插入到输出的 PDF 文件中;并将 citation key 对应的编号算出来;
  4. 第四步,LaTeX Engine 再次运行。此时虽然文末的参考文献列表已经有了,但文中 \cite{...} 某个文献时,具体要转换成数字几(如 Someone said...[1])还写好,依然是问号(Someone said...[??])。最后这一遍根据第三步中的信息,将这个问题敲定。

3. Latexmk

我们在配置自己的 LaTeX 编译环境(例如 VS Code + LaTeX WorkShop,本文暂不涉及环境配置教程,以后可能会写)时,通常会写一个自动化流程,将上述 4 个步骤串起来,一键执行。

还有一个好用的工具 Latexmk 帮我们自动执行这些编译步骤。它会自动监测一个 .tex 文档需要编译多少次,还可以自动调用 PDF 阅读器、监测文件是否在编译后被修改了以至于需要重新编译等。Overleaf 上的一键编译按钮(那个绿色的 Recompile)其实就是调用的 Latexmk

4. Infinite compiler passes (optional)

虽然绝大多数 LaTeX 文档都能在上述 4 次编译后达到最终稳定状态(即,再多次运行 LaTeX Engine,最终的 PDF 也完全不会改变)但上面的多趟编译过程带来了一个有趣的现象(或者说问题):仔细想想,你实际上是无法确定一个 LaTeX 生成的 PDF 文档能在有限次编译后达到最终稳定状态(converge,收敛)的!

LaTeX 支持 \pageref{a},即引用某个 \label{a} 所在的页码。试想,如果源码中有一个 pageref{a} (当然还有很多其他占用空间“恰到好处”的文字),第一遍编译,编译器尚未能解析出文中 \pageref{a} 对应的编号,暂时用 ?? 代替。注意这里的 ?? 是两个字符。同时,在此种情形下,编译器发现 \label{a} 所对应的位置正好在第 9 页的最末尾。于是,编译器在下一趟运行时将会知道,原来的 \pageref{a} 处的 ?? 应该替换成 9。问题来了: ?? 替换成 9这就少了一个字符,可能会导致字符间距的变化,乃至部分字符被换行(我们刚才已经假定,文中还有其他恰到好处的字符,正好将 \label{a} 挤在一个“页面布局稍微有变化就可能被挤到下一行”的地方)!存在这种可能:\label{a} 恰好被挤到了第 10 页!于是,\pageref{a},即 \label{a} 所在的页码,又应该被改成 10。但如果 LaTeX 下一趟把这个第 9 页改成了第 10 页,这就又多了一个字符,同 ?? 的情形一样,\label{a} 可能又被挤回了第 9 页。这里 有具体的例子。

在这种情况下,你可以无限次运行 LaTeX 引擎编译这个文档,而就像一个悖论,永远无法引用到 \label{a} 的正确页码。

Troubleshooting

To Be Continued…