图片

只考虑 pdflatexxelatex 这两种编译引擎,考虑的图片格式包括:矢量图(pdf, eps),位图(png, jpg)。两种编译引擎都直接支持这些图片格式。

includegraphics

LaTeX 本身不支持插图功能,需要 graphicx 宏包提供支持(或者更基础的 graphics 宏包,graphicx 宏包相当于其扩展)

1
\usepackage{graphicx}

导入这个宏包之后,就可以使用 \includegraphics 命令来插入图片

1
\includegraphics[<options>]{<filename>}

例如

1
2
\includegraphics{example.png}
\includegraphics{figure/example.png}

这里的图片文件名可以用相对路径或绝对路径表示,但是文件名里既不要有空格或其它特殊符号,否则宏包在解析文件名的过程中会出错。
图片文件的后缀名一般可以省略不写,编译时会自动补充。

在省略图片文件后缀时,如果存在多个仅后缀不同的图片文件,显然有具体的查找顺序,测试发现似乎会优先选择pdf格式。

对于eps格式的图片,因为涉及到自动转换为pdf格式,所有省略后缀与否可能对pdflatex的编译产生明显影响。pdflatex在编译时会尝试执行类型转换,自动在demo.eps图片所在目录生成demo-eps-converted-to.pdf

完整示例如下

1
2
3
4
5
6
7
8
9
\documentclass{article}
\usepackage{graphicx}

\begin{document}

\noindent
\includegraphics[width=0.5\textwidth]{figure/demo}

\end{document}

graphicx 宏包提供了 \graphicspath 命令,用于声明图片文件的存放目录,在搜索图片时会自动去这些目录中寻找,例如

1
\graphicspath{{./figure/}{./figures/}{./image/}{./images/}{./graphic/}{./graphics/}}

这些目录是常见的存放图片的目录,即使目录不存在也不会报错。

\includegraphics 命令支持如下几个常见选项:

  • width=<width>:将图片缩放到宽度为 <width>
  • height=<height>:将图片缩放到高度为 <height>
  • scale=<scale>:将图片相对于原尺寸缩放 <scale>
  • angle=<angle>:将图片逆时针旋转 <angle>

例如

1
2
% 将图片缩放到宽度为文本宽度的一半,并逆时针旋转 45 度
\includegraphics[width=0.5\textwidth, angle=45]{example.png}

指定宽度是最常见的需求,例如width=0.8\textwidth代表将图片宽度缩放到 80% 的文本宽度。

graphicx 宏包支持草稿选项:当 graphicx 宏包或文档类指定 draft 选项时,图片将不会被实际插入,取而代之的是一个包含文件名的与原图片等大的方框,这有助于加速编译。

浮动体 figure

\includegraphics 命令只能就地插入图片,但是正文中可能包含许多图片和表格等内容,这些内容的尺寸往往太大,导致与文字的排版困难,效果非常混乱,LaTeX 为此引入了浮动体的机制,对浮动体的排版允许脱离原始的上下文,自动填入合适的位置。

LaTeX 预定义了两类浮动体环境:figuretable。习惯上在 figure 中放置图片,在 table 中放置表格,但并没有严格限制,实际上可以在其中放置任意内容。两种浮动体环境的用法类似,下面讨论 figure 环境。

figure环境被用来包裹最基本的命令,得到浮动的图片,例如

1
2
3
4
\begin{figure}[htbp]
\centering
\includegraphics[width=0.8\textwidth]{demo.png}
\end{figure}

习惯上对于浮动图片都会使用\centering命令使图片水平居中放置。

浮动体的使用主要是如下的几个浮动规则参数:

  1. h (here):允许放置在当前上下文位置
  2. t (top):允许放置在当前页或后续页面的顶部
  3. b (bottom):允许放置在当前页或后续页面的底部
  4. p (page):允许放置在一个独立页面中

我们可以选择开启其中的部分规则,例如[hbp]就指定允许基于这三种规则来放置浮动体,默认设置为tbp

可以在选项中加上!来忽略LaTeX对浮动体排版的默认限制,默认限制包括:

  • 浮动体个数:除单独成页外,默认每页不超过 3 个浮动体,其中顶部不超过 2 个,底部不超过 1 个。
  • 浮动体空间占页面的百分比:默认顶部不超过 70%,底部不超过 30% 。

需要注意的是:在实际排版时,对浮动体放置规则的选择顺序是固定的(h-t-b-p),与写法无关,例如[!htp][ph!t]是完全一样的。

在双栏排版环境下,LaTeX 还提供了 table*figure* 环境用来排版跨栏的浮动体,它们的用法与 tablefigure 类似,不同之处为横跨双栏的浮动体只允许使用tp 两个规则。

float 宏包为浮动体环境提供了 H 位置参数,不要与 htbp! 混用。使用 H 位置参数时,会取消浮动机制,将浮动体视为一般的盒子插入当前位置。

浮动体提供了 \caption 命令添加标题,此时会自动给浮动体编号,\caption 命令之后还可以紧跟 \label 命令标记交叉引用。
例如

1
2
3
4
5
\begin{figure}[htbp]
\centering
\includegraphics{demo.png}
\caption{Title}\label{fig:1}
\end{figure}

一个值得注意的细节:\caption 命令的位置也比较重要:

  • 如果 \caption 命令放在 \includegraphics 命令之前,则标题在图片上方;
  • 如果 \caption 命令放在 \includegraphics 命令之后,则标题在图片下方。

习惯上,图片的标题建议放置在图片下方,而表格的标题建议放置在表格上方。

与编号的\caption命令类似,\caption* 命令会生成不带编号的标题,但是这个命令是caption宏包提供的。

下面是实践中常用的插入图片的代码片段:

  • 不含标题的图片
1
2
3
4
\begin{figure}[htbp]
\centering
\includegraphics[width=0.8\textwidth]{demo.png}
\end{figure}
  • 含有标题和标签的图片
1
2
3
4
5
\begin{figure}[htbp]
\centering
\includegraphics[width=0.8\textwidth]{demo.png}
\caption{Results}\label{fig:result1}
\end{figure}

多个子图

我们时常有在一个浮动体里面放置多张图的用法(通常是并列子图),有非常多的方法可以实现这样的效果,有的不依赖宏包,有的依赖宏包。

第一种方案如下,直接在一个浮动体中使用两次\includegraphics命令,在两个图片之间需要指定间距

1
2
3
4
5
6
7
\begin{figure}
\centering
\includegraphics[width=...]{a.jpg}
\hspace{1in} % or \qquad
\includegraphics[width=...]{b.jpg}
\caption{Demo}
\end{figure}

但是这种方案只能使用一个\caption命令,只有一个图片标题(和标签),对两个宽度和间距需要具体调整。

第二种方案如下,创建两个minipage环境,此时两个图片可以有独立的标题和标签

1
2
3
4
5
6
7
8
9
10
11
12
\begin{figure}
\begin{minipage}{...}
\centering
\includegraphics[width=...]{a.jpg}
\caption{Demo 1}\label{fig:a}
\end{minipage}
\begin{minipage}{...}
\centering
\includegraphics[width=...]{b.jpg}
\caption{Demo 2}\label{fig:b}
\end{minipage}
\end{figure}

这种方案也比较麻烦,需要控制很多的宽度。

上面两种方案看起来都太原始了,当我们需要更进一步,给每个图片定义小标题时,就要通过 subcaption 宏包实现。

1
\usepackage{subcaption}

实际上有很多宏包可以实现多个子图的功能,例如subfigure(版本过旧,被弃用),subfigsubcaption。这些宏包相互之间是冲突的,有的版本过旧已经被舍弃,目前只建议使用subcaption

使用 subcaption 宏包可以给每个子图定义单独的标题和标签,子图的编号与父图的编号具有上下级关系,例如Fig. 3.1(a)

使用 subcaption 宏包的代码片段如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
\begin{figure}[htbp]
\centering
\begin{subfigure}[b]{0.47\textwidth}
\centering
\includegraphics[width=\textwidth]{a.png}
\caption{Demo a}\label{fig:subfig-a}
\end{subfigure}
\hfill
\begin{subfigure}[b]{0.47\textwidth}
\centering
\includegraphics[width=\textwidth]{b.png}
\caption{Demo b}\label{fig:subfig-b}
\end{subfigure}
\caption{Demo}\label{fig:example}
\end{figure}

其中的\hfill命令会插入弹性的水平间距,比固定的\qquad更合适。

虽然subfigure宏包已经被弃用了,但是鉴于我之前使用过,还是记录一下对应的代码片段。

1
2
3
4
5
6
7
8
9
10
11
12
% \usepackage{subfigure}

\begin{figure}[htbp]
\centering
\subfigure[Demo a]{
\includegraphics[width=7.25cm]{a.png}
}
\subfigure[Demo b]{
\includegraphics[width=7.25cm]{b.png}
}
\caption{Demo}\label{fig:1}
\end{figure}

给透明图片添加底色

有时图片可能是透明的,直接放置到现有背景上可能难以辨识,可以给图片加一个底色,例如

1
2
3
4
5
6
\begin{figure}[htbp]
\centering
{\colorbox{white}
\includegraphics[width=0.8\textwidth]{demo.png}
}
\end{figure}

表格

在 LaTeX 中,表格功能由 tabular 环境提供,与图片类似的浮动机制由 table 环境提供。常见的表格类型包括普通文本表格、带线表格、跨列/跨行表格、以及更复杂的长表格和三线表格。

tabular

LaTeX 内置的 tabular 环境用于创建最基本的表格,其语法为

1
2
3
4
\begin{tabular}{<alignment>}
<cell_11> & <cell_12> & ... \\
<cell_21> & <cell_22> & ... \\
\end{tabular}

其中 <alignment> 用于指定每一列的对齐方式:

  • l:左对齐(left)
  • c:居中对齐(center)
  • r:右对齐(right)

在表格内部:

  • 每一行的单元格之间使用 & 分隔,实际使用的单元格数目可以等于或少于 <alignment> 声明的列数,此时后面的单元格留空;
  • 表格内部的正常换行不会对应表格换行,必须每一行的末尾使用 \\ 手动换行(包括最后一行)。

一个简单示例如下:

1
2
3
4
5
\begin{tabular}{lcr}
A & B & C \\
AA & BB & CC \\
AAA & BBB & CCC \\
\end{tabular}

输出为一个三行三列的表格,依次为左中右对齐,不含任何边框线。

<alignment> 还支持更丰富的效果,包括:

  • p{<width>}:固定单元格的宽度,如果超出会自动折行
  • @{<string>}:在单元格之间插入固定的文本内容,如果留空 @{} 则会消除默认的单元格之间的间距,例如 @{}ccc@{} 可以消除表格两端的空白。

添加边框线

<alignment> 中使用 | 可以添加竖线,在表格内部使用 \hline 可以添加横线。例如:

1
2
3
4
5
6
7
8
\begin{tabular}{|l|c|r|}
\hline
A & B & C \\
\hline
AA & BB & CC \\
AAA & BBB & CCC \\
\hline
\end{tabular}

这样就会得到一个带边框和一些横线的表格。

除了跨越整个表格的横线命令 \hline,还有更灵活的横线命令 \cline,接受两个参数指定横线开始和结束的列(闭区间,允许两个值相同)

1
\cline{i-j}

例如

1
2
3
4
5
6
7
8
9
\begin{tabular}{|c|c|c|}
\hline
4 & 9 & 2 \\
\cline{2-3}
3 & 5 & 7 \\
\cline{1-1}
8 & 1 & 6 \\
\hline
\end{tabular}

浮动体 table

tabular 很少直接使用,通常嵌入浮动体环境 table 中,用法与图片对应的 figure 环境类似。

例如

1
2
3
4
5
6
7
8
9
10
11
12
\begin{table}[htbp]
\centering
\caption{Demo Table}\label{tab:demo}
\begin{tabular}{|l|c|r|}
\hline
A & B & C \\
\hline
AA & BB & CC \\
AAA & BBB & CCC \\
\hline
\end{tabular}
\end{table}

浮动规则 [htbp]、标题命令 \caption 与图片浮动体一致。

习惯上在浮动体中将图片的标题放置在下方,而表格的标题则放置在上方。

有时需要在表格下面添加一些简短的注释,可以在 table 环境内部使用脚注字体实现,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
\begin{table}[htbp]
\centering
\caption{Demo Table}\label{tab:demo}
\begin{tabular}{|l|c|r|}
\hline
A & B & C \\
\hline
AA & BB & CC \\
AAA & BBB & CCC \\
\hline
\end{tabular}

\vspace{2ex}
{\footnotesize xxx,yyyy,zzz}
\end{table}

合并单元格

表格排版的一个常见需求是合并单元格,包括横向合并与纵向合并。由于 LaTeX 默认按行生成表格,横向合并较为简单,纵向合并则更加复杂,需要额外的宏包支持。

拆分单元格也是一个基本需求,但是在实现上会更加复杂,建议尽量用单元格合并替代单元格拆分。

横向合并

使用 \multicolumn 命令即可完成单元格的横向合并,命令语法如下

1
\multicolumn{<n>}{<alignment>}{<content>}

参数依次为合并的列数,格式以及内容。

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
\begin{table}[htbp]
\centering
\begin{tabular}{|c|c|c|}
\hline
1 & 2 & 3(Center) \\
\hline
\multicolumn{2}{|c|}{12} &
\multicolumn{1}{r|}{3(Right)} \\
\hline
1 & \multicolumn{2}{c|}{23} \\
\hline
\end{tabular}
\end{table}

形如 \multicolumn{1}{<column-spec>}{<item>} 的命令没有进行合并,但是可以用来局部调整某一个单元格的列格式。

纵向合并

纵向合并单元格的命令 \multirowmultirow 宏包提供

1
\usepackage{multirow}

命令语法如下

1
\multirow{<nrows>}{<width>}{<content>}

参数依次为合并的行数,宽度以及内容,宽度参数通常填 * 以使用自然宽度,在其它行被合并的单元格位置需要留空。

注意在涉及到纵向合并的位置,\hline 仍然穿越单元格,因此需要使用 \cline 替换。

例如

1
2
3
4
5
6
7
8
9
10
\begin{table}[htbp]
\centering
\begin{tabular}{c|cc}
\hline
\multirow{2}{*}{S} & B1 & C1 \\
\cline{2-3}
& B2 & C2 \\
\hline
\end{tabular}
\end{table}

有一个名称非常类似的宏包 multicol,但是它不是用在表格中,它提供的环境 multicols 用于在正文中创建多栏布局。

三线表

LaTeX 默认的表格线较粗、视觉效果一般,在某些领域更喜欢使用标准的三线表,可以使用 booktabs 宏包

1
\usepackage{booktabs}

此时可以使用几种不同的横线:\toprule(顶线),\midrule(中线),\bottomrule(底线),顶线和底线相比中线更粗。例如

1
2
3
4
5
6
7
8
9
10
11
12
\begin{table}[htbp]
\centering
\caption{Comparison of methods}\label{tab:compare}
\begin{tabular}{lcc}
\toprule
Method & Accuracy & Time (s) \\
\midrule
A & 95.2 & 12.5 \\
B & 93.8 & 10.9 \\
\bottomrule
\end{tabular}
\end{table}

在三线表中,不建议与竖线 | 混用,可以保持排版简洁。

其实这里的粗线和细线也可以通过 \hline[1pt]\hline[0.6pt] 直接实现。

一些例子

下面提供一些更完整的表格例子。(参考 Tips for Writing a Research Paper using LaTeX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
\begin{table}[htbp]
\centering
\caption{A simple table with a header row.}
\label{tab:table1}
\begin{tabular}{*{10}{c}}
\toprule
Data & Size & 2-Exp & 3-Exp & 4-Exp & 5-Exp & 6-Exp & 7-Exp \\
\midrule
A & $1280\times 720$ & 1 & 2 & 3 & 4 & 5 & 4 \\
B & $1280\times 720$ & 1 & 2 & 3 & 4 & 5 & 4 \\
Ours & $4096\times 2168$ & 2 & 3 & 4 & 6 & 5 & 4 \\
\bottomrule
\end{tabular}
\end{table}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
\begin{table}[htbp]
\centering
\caption{A table with multi-column headers.}\label{tab:table2}

\begin{tabular}{*{10}{c}}
\toprule
& & \multicolumn{2}{c}{$6-9$ frames} & \multicolumn{2}{c}{$5-7$ frames} & \multicolumn{2}{c}{$50-200$ frames} \\
Data & Size & 2-Exp & 3-Exp & 2-Exp & 3-Exp & 2-Exp & 3-Exp \\
\midrule
A & $1280\times 720$ & 1 & 2 & 3 & 4 & 5 & 4 \\
B & $1280\times 720$ & 1 & 2 & 3 & 4 & 5 & 4 \\
Ours & $4096\times 2168$ & 2 & 3 & 4 & 6 & 5 & 4 \\
\bottomrule
\end{tabular}
\end{table}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% \usepackage{makecell}
\begin{table}[htbp]
\centering
\caption{A table with line break in the header. Line break is useful if the item name is too long.}
\label{tab:table3}
\begin{tabular}{*{10}{c}}
\toprule
& & \multicolumn{3}{c}{$6-9$ frames} & \multicolumn{3}{c}{$5-7$ frames} \\
Data & Size & \makecell{2-Exp \\ Scenes} & \makecell{2-Exp \\ Scenes} & \makecell{2-Exp \\ Scenes} & \makecell{2-Exp \\ Scenes} & \makecell{2-Exp \\ Scenes} & \makecell{2-Exp \\ Scenes} \\
\midrule
A & $1280\times 720$ & 1 & 2 & 3 & 4 & 5 & 4 \\
B$^*$ & $1280\times 720$ & 1 & 2 & 3 & 4 & 5 & 4 \\
Ours & $4096\times 2168$ & 2 & 3 & 4 & 6 & 5 & 4 \\
\bottomrule
\end{tabular}

\vspace{1em}
\footnotesize ${^*}$ indicates that method B is trained from scratch.
\end{table}

这个例子使用了 \rowcolor 给表格指定的行加颜色,需要导入额外的宏包 colortbl。(如果直接包起来改颜色,对 & 的解析可能会出错)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% \usepackage{colortbl}
\begin{table}[htbp]
\centering
\caption{A table with parallel lines for grouping and color highlight.}\label{tab:ablation_study}
\begin{tabular}{*{2}{l}||*{2}{c}||*{2}{c}||*{2}{c}}
\toprule
\multicolumn{1}{c}{} & \multicolumn{1}{c||}{} & \multicolumn{2}{c||}{Synthetic Dataset} & \multicolumn{2}{c||}{Static Data} & \multicolumn{2}{c}{Dynamicgt Data} \\
ID & Method & PSNRT & VDP & PSNRT & VDP & PSNRT & VDP \\
\midrule
0 & ANet & 39.25 & 70.81 & 40.62 & 74.51 & 44.43 & 77.74 \\ %
1 & BNet & 39.69 & 70.95 & 37.61 & 75.30 & 43.70 & 78.97 \\
\rowcolor{gray!30}
2 & ANet + BNet & \textbf{40.34} & \textbf{71.79} & \textbf{41.18} & \textbf{76.15} & \textbf{45.46} & \textbf{79.09} \\
\midrule
3 & ANet + BNet w/o C & 39.72 & 71.38 & 40.52 & 74.79 & 45.09 & 78.24 \\ %
4 & ANet + BNet w/o D & 40.03 & 71.66 & 40.80 & 76.12 & 45.17 & 78.99 \\ %
\bottomrule
\end{tabular}
\end{table}

列表

基本使用

无序列表 itemize,默认使用黑点

1
2
3
4
\begin{itemize}
\item First item
\item Second item
\end{itemize}

有序列表 enumerate,默认使用 1. 编号

1
2
3
4
\begin{enumerate}
\item First item
\item Second item
\end{enumerate}

\item 命令可以提供一个可选参数,用于将有序列表的编号或者无序列表的符号替换成自定义内容。

1
2
3
4
\begin{enumerate}
\item A nested item.\label{itref}
\item[*] A starred item.
\end{enumerate}

还可以直接设置为空来删除符号(不影响缩进)

1
2
3
4
\begin{enumerate}
\item[] Item 1
\item[] Item 2.
\end{enumerate}

两种列表支持嵌套,也可以互相嵌套

1
2
3
4
5
6
7
8
\begin{enumerate}
\item First
\begin{itemize}
\item Subitem 1
\item Subitem 2
\end{itemize}
\item Second
\end{enumerate}

在各级列表中,对应的符号和编号样式也有所不同:

  • 无序列表:依次为黑色圆点,-*.
  • 有序列表:依次为 1.(a)i.A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
\begin{multicols}{2}
\begin{itemize}
\item X1
\item X2
\begin{itemize}
\item X11
\item X12 \begin{itemize}
\item X121
\item X122 \begin{itemize}
\item X1221
\item X1222
\end{itemize}
\end{itemize}
\end{itemize}
\end{itemize}
\begin{enumerate}
\item X1
\item X2
\begin{enumerate}
\item X11
\item X12 \begin{enumerate}
\item X121
\item X122 \begin{enumerate}
\item X1221
\item X1222
\end{enumerate}
\end{enumerate}
\end{enumerate}
\end{enumerate}
\end{multicols}

补充:

  • 各级无序列表的符号由命令 \labelitemi\labelitemiv 定义,可以简单地重新定义它们;
  • 各级有序列表的符号由命令 \labelenumi\labelenumiv 定义,重新定义这些命令需要用到计数器。

除此之外,还有一个类似的关键字列表,此时 \item[] 的可选参数一般是必选的,并且会被加粗。(description的默认缩进看着非常奇怪,通常需要手动调整)

1
2
3
4
\begin{description}[leftmargin=1cm]
\item[Enumerate] Numbered list.
\item[Itemize] Non-numbered list.
\end{description}

itemizeenumeratedescription 环境都是基于广义列表环境 list 实现的。

修改列表样式

LaTeX 本身并未提供方便的列表样式定制功能,通常使用 enumitem 宏包定制。

1
\usepackage{enumitem}

最基础的样式需求是修改列表编号和符号,可以通过 label=... 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
\begin{enumerate}[label=\arabic*.] % 1.
\item Item 1
\item Item 2
\end{enumerate}

\begin{enumerate}[label=\alph*.] % a.
\item Item 1
\item Item 2
\end{enumerate}

\begin{enumerate}[label=\roman*.] % i.
\item Item 1
\item Item 2
\end{enumerate}

\begin{itemize}[label=\textbullet] % •
\item Item 1
\item Item 2
\end{itemize}

\begin{itemize}[label=--] % –
\item Item 1
\item Item 2
\end{itemize}

\begin{itemize}[label=\textasteriskcentered] % *
\item Item 1
\item Item 2
\end{itemize}

如果给 enumitem 宏包加上 shortlabels 选项,就可以更简单地实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
\begin{enumerate}[1.]
\item Item 1
\item Item 2
\end{enumerate}

\begin{enumerate}[(a)]
\item Item 1
\item Item 2
\end{enumerate}

\begin{enumerate}[i.]
\item Item 1
\item Item 2
\end{enumerate}

\begin{itemize}[-]
\item Item 1
\item Item 2
\end{itemize}

\begin{itemize}[*]
\item Item 1
\item Item 2
\end{itemize}

与缩进和空格相关的参数主要包括:

  • leftmargin 列表整体的左缩进
    • leftmargin=* 自动计算缩进,使列表与父环境左对齐,而不是固定缩进
  • topsep 列表与上下文段落的垂直间距
  • itemsep 每项之间的垂直间距

例如移除左缩进

1
2
3
4
5
\begin{enumerate}[leftmargin=*]
\item Item 1
\item Item 2
\item Item 3
\end{enumerate}

移除各项之间的垂直间距

1
2
3
4
5
\begin{enumerate}[itemsep=0pt]
\item Item 1
\item Item 2
\item Item 3
\end{enumerate}

对于嵌套列表,可以为每层嵌套单独调整 leftmargin,避免缩进过深或过浅。

1
2
3
4
5
6
7
8
\begin{enumerate}
\item First
\begin{itemize}[leftmargin=1.5em]
\item Subitem 1
\item Subitem 2
\end{itemize}
\item Second
\end{enumerate}

可以使用 \setlist 命令对列表样式进行全局配置。

1
2
3
4
\usepackage{enumitem}

\setlist[itemize]{label=--, leftmargin=2em, itemsep=0.5em}
\setlist[enumerate]{label=\arabic*., leftmargin=2em, itemsep=0.3em}

算法环境 algorithm2e

介绍

在LaTeX中使用伪代码来描述算法是常见的需求,LaTeX其实有很多类似名称的宏包,简单辨析一下:(参考latex 中 algorithm、algorithmic、algorithmicx、algorithm2e 的区别

  • algorithm 用来封装算法:给算法加上标题(caption)和标签(label),方便进行索引;
  • algorithmic 相当于算法的内容物;
  • algorithmicx 相当于 algorithmic 的升级版;
  • algpseudocodealgorithmicx 的一种呈现方式,完整包含了algorithmicx
  • algorithm2ealgorithmicxalgorithmic 类似,也是用来描述算法的;但是其语法不如 algorithmicx 直白。

总得来说,通常有两条路线可以选择:

  1. algorithm2e宏包搞定
  2. algpseudocodealgorithmic等撰写算法本身,再用algorithm包给算法加标题

本文主要考虑第一个方式——使用algorithm2e宏包。

在导入algorithm2e宏包时,通常会顺便加上一些选项,例如

1
2
3
4
\usepackage[ruled,linesnumbered,noline]{algorithm2e}
% ruled 上下添加横线,类似于三线表,此时标题在上面
% linesnumbered 显示行数
% noline 关闭默认体现层级的竖线

算法环境的主体框架如下,通常会添加标题(caption)和标签(label

1
2
3
4
5
6
\begin{algorithm}
\caption{XXX}\label{alg1}

...

\end{algorithm}

一个简单的例子:欧几里得算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
\documentclass{article}
\usepackage[ruled,linesnumbered,noline]{algorithm2e}

\begin{document}

\begin{algorithm}
\caption{Euclid's algorithm}
\KwData{Two nonnegative integers $a$ and $b$}
\KwResult{Their greatest common divisor $d = \gcd(a, b)$}
\While{$b \neq 0$}{
$r \leftarrow a \bmod b$\;
$a \leftarrow b$\;
$b \leftarrow r$\;
}
$d \leftarrow a$\;
\end{algorithm}

\end{document}

在导入时使用[ruled,linesnumbered,noline]选项,得到的效果如下图

与之相比,使用默认选项的效果就差很多,并不符合文献中的主流风格

基本使用

以欧几里得算法为例

1
2
3
4
5
6
7
8
9
10
11
\begin{algorithm}
\caption{Euclid's algorithm}
\KwData{Two nonnegative integers $a$ and $b$}
\KwResult{Their greatest common divisor $d = \gcd(a, b)$}
\While{$b \neq 0$}{
$r \leftarrow a \bmod b$\;
$a \leftarrow b$\;
$b \leftarrow r$\;
}
$d \leftarrow a$\;
\end{algorithm}

在算法环境中,每一行语句可以使用\;结尾,这会在文本中加上分号结尾并换行,还可以使用\\强行换行。
除此之外,下面的某些固定语法结构也会导致换行。
算法内部需要数学表达式时,仍然需要$$等来创建数学环境。

算法通常需要使用下面这些预定义宏:

  • IO:
    • 输入:\KwIn{<input>}
    • 数据:\KwData{<input>}
    • 输出:\KwOut{<output>}
    • 结果:\KwResult{<output>}
  • 基础:
    • to: \KwTo
    • 返回值:\KeRet{<value>}或等效的\Return{<value>}
  • 循环:包括下面几种形式,其中含l-前缀的版本是行内形式,即内部不产生换行(但结尾仍然有换行)
1
2
3
4
5
6
7
8
9
\For{<condition>}{<text loop>}
\While{<condition>}{<text loop>}
\ForEach{<condition>}{<text loop>}
\ForAll{<condition>}{<text loop>}

\lFor{<condition>}{<text loop>}
\lWhile{<condition>}{<text loop>}
\lForEach{<condition>}{<text loop>}
\lForAll{<condition>}{<text loop>}
  • 条件:包括下面几种形式,默认形式会产生换行并使用end结尾;含l-前缀的版本是行内形式,即内部不产生换行(但结尾仍然有换行);含e-前缀的eIf自带真假两个分支;含u-前缀的版本是未完成形式,即结尾不含end,需要搭配其他命令来收尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
\If{<condition>}{<then block>}
\ElseIf{<elseif block>}{<then block>}
\Else{<else block>}

\lIf{<condition>}{<then block>}
\lElseIf{<elseif block>}{<then block>}
\lElse{<else block>}

\uIf{<condition>}{<then block>}
\uElseIf{<elseif block>}{<then block>}
\uElse{<else block>}

\eIf{<condition>}{<then block>}{<else block>}

\leIf{<condition>}{<then block>}{<else block>}
  • 注释:默认支持两类注释,即tcc(C语言风格/**/)和tcp(C++风格//)注释,有几个选项会调整注释的细节,这里略去
1
2
3
4
5
6
7
8
9
10
11
12
13
\tcc{<comment>}
\tcc*{<comment>}
\tcc*[r]{<comment>}
\tcc*[l]{<comment>}
\tcc*[h]{<comment>}
\tcc*[f]{<comment>}

\tcp{<comment>}
\tcp*{<comment>}
\tcp*[r]{<comment>}
\tcp*[l]{<comment>}
\tcp*[h]{<comment>}
\tcp*[f]{<comment>}

对于循环和条件结构的呈现,还涉及到如下几个选项:

  • lined:伪代码的 start-end 之间用竖线相连
  • vlined:伪代码的 start-end 之间用竖折线相连,其中折线代替了end的作用
  • noline:伪代码的 start-end 之间没有线相连

这几个选项既可以在宏包选项中设置,也可以使用下面的命令设置

1
2
3
\SetAlgoLined
\SetAlgoVlined
\SetAlgoNoline

在循环和条件结构中,都支持加上括号包裹的注释选项,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
\lIf(\tcc*[h]{lif comment}){test}{
text
}
\uIf(\tcc*[f]{uif comment}){test}{
then text\;
}
\uElseIf(\tcc*[f]{comment}){test}{
elseif text\;
}
\lElseIf(\tcc*[h]{comment}){test}{
text
}
\lElse(\tcc*[f]{comment}){text}

宏包还支持自定义一些宏,例如下面定义了与IO宏类似的一个命令\KwPara{<input>},代表算法需要的各种细节参数

1
2
3
\SetKwInput{KwPara}{Parameter}

\KwPara{m,n,$\varepsilon$}

除了这个宏包之外,还可以使用其他方式来实现,例如导入algorithmalgpseudocode这两个宏包,本文暂不讨论具体细节,不过可以明确的是,它的命令风格与algorithm2e是截然不同的。

抄录环境 verbatim

LaTeX直接提供verbatim抄录环境,在其中以等宽字体按照原样排版代码,回车和空格都是正常的换行和空位的作用,缩进也是原样保留的。使用示例如下

1
2
3
4
5
6
7
8
\begin{verbatim}
#include <iostream>
int main()
{
std::cout << "Hello, world!\n";
return 0;
}
\end{verbatim}

效果如下图

注意这不是代码环境,因为它只是忠实地保持原样输出,并没有提供常见的语法高亮功能。
还有与之类似的verbatim*环境,它更进一步将空格显示为

与之类似的,还有展示行内抄录片段或关键字的\verb命令

1
\verb<delim><code><delim>

与常见的命令不同,\verb需要提供自定义的分界符delim来标明代码的前后分界位置,要求前后分界符必须一致,除字母、空格或星号外,可任意选择不与代码自身冲突的符号(习惯上可以使用|符号),例如

1
2
3
4
5
\verb|a\ b|         % 使用|作为分界符 a\ b

\verb+(a || b)+ % 使用+作为分界符 (a || b)

\verb+\ldots+ % 使用+作为分界符 \ldots

代码环境 listings

介绍

这里指的是展示编程语言的源代码片段的环境,与前面的算法环境/抄录环境都是不一样的。

listings宏包是一个功能强大也非常复杂的宏包,用于提供特定编程语言的语法高亮。

1
\usepackage{listings}

下面是最简单的例子,在文档中展示了一段Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
\documentclass{article}
\usepackage{listings}

\begin{document}

\begin{lstlisting}[language=Python, caption=Demo]
def helloworld():
print("Hello, world!")

helloworld()
\end{lstlisting}

\end{document}

效果如下图(具体效果可能和不同的编译器有关,下图是使用xelatex编译的结果)

除了listingsminted宏包是另一种常见语法高亮方案,它基于Python提供的支持进行语法高亮,在编译时需要传入特殊的选项-shell-escape,暂略。

基本使用

listings宏包最主要的用法是使用lstlisting环境导入代码片段

1
2
3
\begin{lstlisting}
<codes>
\end{lstlisting}

可以加上一些选项,例如指定语言

1
2
3
\begin{lstlisting}[language=Python]
<codes>
\end{lstlisting}

提供的语言类型会被用于识别关键字、字符串、注释等,进而影响具体的显示效果。

listings宏包还支持从源代码文件中直接导入代码,需要使用专门的\lstinputlisting命令

1
\lstinputlisting[language=Python]{main.py}

\lstinputlisting命令默认会导入所有代码,可以设置起止行号来导入源文件中的部分片段

1
\lstinputlisting[language=Python, firstline=2, lastline=12]{main.py}

补充:\lstlistoflistings命令会将所有添加caption的代码片段生成代码目录。

除此之外,listings宏包还提供\lstinline命令用于行内代码片段展示:\lstinline|<codes>|
\verb命令类似,它的分界符可以是任意的,从而避免与代码中的字符冲突,例如

1
2
3
\lstinline!var i:integer;!

\lstinline|int x=1;|

配置

listings宏包默认的显示效果很丑,需要经过一番配置才能达到美观的语法高亮显示效果。
配置既可以在具体的环境开头指定,也可以在导言区进行全局默认设置。

对于具体环境的配置通过可选参数给出,例如

1
2
3
4
5
6
7
8
9
10
11
\begin{lstlisting}[
language=Python, % 语言
numbers=left, % 行号显示位置
firstnumber=1 % 行号开始
]
# Python
def hello():
print("Hello, world!")

hello()
\end{lstlisting}

在导言区进行全局默认配置,也就是修改默认样式,例如

1
2
3
4
5
6
7
8
\lstset{
language=Python, % 设置语言
columns = fixed, % 列距排版
basicstyle = \linespread{0.8} \ttfamily, % 设置行距,字体
numbers = left, % 行号显示设置
frame = single, % 背景边框
showstringspaces = false, % 字符串中的空格显示
}

此时就不需要在每个环境中进行配置了。

除此之外,还可以重新定义一套新的样式(而非修改默认样式),例如针对特定的语言进行配置

1
2
3
\lstdefinestyle{cppStyle}{
...
}

对自定义样式的使用通过style选项指定

1
2
3
4
5
6
7
8
\begin{lstlisting}[style=cppStyle]
#include <iostream>

int main() {
std::cout << "Hello, world!\n";
return 0;
}
\end{lstlisting}

还可以在导言区使用下面的命令将新定义的样式设置为默认样式

1
\lstset{style=cppStyle}

在更丰富的配置中通常会搭配使用xcolor宏包,定义新的颜色用于代码高亮,例如

1
2
3
4
5
6
7
\usepackage{xcolor}

\definecolor{dkgreen}{rgb}{0,0.6,0}
\definecolor{gray}{rgb}{0.5,0.5,0.5}
\definecolor{mauve}{rgb}{0.58,0,0.82}

\definecolor{epubblue}{RGB}{1,126,218}

颜色的使用例如\color{gray}。注意上面的小写rgb接受的参数为0-1的小数值,而大写RGB接受的是0-256的整数值。

下面是listings宏包支持的常见配置选项:

  • 语言设置:language=Python,C++,支持多个语言,注意C++的名称要用C++而非Cpp
  • 基本样式设置basicstyle = \ttfamliy,这是指定等宽字体(符合编程习惯)
  • 样式:主要是颜色和字体设置,针对关键词,字符串和注释
    • 关键字样式:keywordstyle=\bfseries \color[RGB]{40,40,255},通常指定一个颜色
    • 注释样式:commentstyle=\color[RGB]{0,96,96}, 通常指定一个颜色
    • 字符串样式:stringstyle=\color[RGB]{128,0,0}, 通常指定一个颜色
    • 行号样式:numberstyle=\footnotesize,这可以让行号的字体更小一点
    • 标识符样式:identifierstyle=\color{black},标识符的颜色字体等
  • 补充关键词:morekeywords={eg1,eg2},这可以加入自定义关键词,默认的不太全
  • 自动换行:breaklines=true,建议,否则过长的行会直接截断!
  • 行号显示:
    • 是否显示行号:numbers=left,支持left/right/none,默认为none不显示
    • 间隔多少行显示行号:stepnumber=5
    • 行号起始:firstnumber=10,设置为last则继承上一个代码环境的行号
  • 列间距设置:columns=flexible,让不同列之间的距离自适应,反之可以使用fixed设置固定距离
  • 字符串中的空格显示:showstringspaces=false,否则默认会对字符串内的空格显示下划线
  • tab与缩进:
    • 突出显示tab:showtabs=true,会将tab显示为长的下划线,默认不显示(只展示连续空格)
    • tab长度:tabsize=8,默认长度为8个空格
  • 边框:frame=single,single显示单边框,shadowbox为阴影框,默认是none无边框
  • 背景颜色:backgroundcolor=xxx
  • 代码块标记:name=xxx,同名的代码块行号会自动延续
  • 代码块显示名称:caption=Name,还可以指定名称的显示位置(默认在顶部),例如captionpos=b改在底部,caption会自动编号,如果不需要编号,可以使用title=xxx,如果同时出现则会显示后设置的项
  • 忽略部分字符以支持latex命令:escapeinside={\%*}{*)}

下面是一份参考的语法高亮配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
\usepackage{listings}
\usepackage{xcolor}

\lstdefinestyle{simpleStyle}{
basicstyle=\ttfamily\small, % 设置字体族
breaklines=true, % 自动换行
keywordstyle=\color{blue}, % 关键字样式
identifierstyle=\color{black}, % 标识符样式
stringstyle=\color{violet}, % 字符串样式
commentstyle=\color[RGB]{34,139,34}, % 注释样式
showstringspaces=false, % 不显示字符串中的空格
numbers=left, % 显示行号在左边
numbersep=2em, % 设置行号的具体位置
numberstyle=\footnotesize, % 缩小行号
frame=single, % 边框
framesep=1em, % 设置代码与边框的距离
}

\lstset{style=simpleStyle}

这里并没有指定语言,因此每一个代码块必须要指定语言才能有更好的显示效果。

下面依次是MATLAB,Python,C的使用示例以及对应的显示效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
\begin{lstlisting}[language=Matlab, caption=MATLAB Example]
% Call function to compute first 10 terms
fibonacci(10);

function fibonacci(n)
% Calculate the first n Fibonacci numbers
fib = zeros(1, n); % Initialize array
fib(1) = 0; % First term
if n > 1
fib(2) = 1; % Second term
end

for i = 3:n
fib(i) = fib(i-1) + fib(i-2); % Current term
end

% Display results
fprintf('First %d Fibonacci numbers:\n', n);
disp(fib);
end
\end{lstlisting}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
\begin{lstlisting}[language=Python, caption=Python Example]
def fibonacci(n):
"""Calculate the Fibonacci sequence up to n."""
fib = [0, 1] # Initialize the list
while len(fib) < n:
fib.append(fib[-1] + fib[-2]) # Append the next Fibonacci number
return fib

# Get user input
num = int(input("Enter the number of terms: "))
result = fibonacci(num)

# Display the result
print(f"The first {num} Fibonacci numbers are: {result}")

\end{lstlisting}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
\begin{lstlisting}[language=C, caption=C Example]
#include <stdio.h>

void fibonacci(int n) {
// Calculate Fibonacci numbers up to n
int fib[n];
fib[0] = 0;
fib[1] = 1;

for (int i = 2; i < n; i++) {
fib[i] = fib[i - 1] + fib[i - 2]; // Calculate the next Fibonacci number
}

// Print the results
printf("The first %d Fibonacci numbers are:\n", n);
for (int i = 0; i < n; i++) {
printf("%d ", fib[i]);
}
printf("\n");
}
\end{lstlisting}

hyperref 宏包使用

使用 hyperref 宏包可以定制交叉引用和超链接样式。

1
\usepackage{hyperref}

这个宏包容易和其它宏包产生冲突,建议将其放在靠后的位置导入,否则可能导致跳转错误。

可以用下面的命令生成可点击的超链接

1
2
\url{url}
\url{https://www.baidu.com}

可以在添加超链接时,设置显示的文本内容

1
2
\href{url}{text}
\href{https://www.baidu.com}{Baidu}

可以用下面的命令设置样式

1
\hypersetup{colorlinks=true,linkcolor=,urlcolor=cyan}

也可以在宏包加载时直接传递样式选项

1
\usepackage[colorlinks=true,linkcolor=,urlcolor=cyan]{hyperref}

解释一下几个常见的选项:

选项 含义 说明
colorlinks 是否用颜色标记链接(若为 false,则显示彩色方框) 建议设为 true,避免方框,默认 false
linkcolor 内部链接颜色(章节、图表、公式等引用) 控制 \ref\autoref 等颜色
citecolor 文献引用颜色 控制 \cite 链接颜色
urlcolor URL 链接颜色 控制 \url\href 等外部链接
filecolor 文件链接颜色 用于指向本地文件的链接
hidelinks 隐藏所有颜色与边框 适合正式论文或投稿版本

需要特别注意的是:如果在章节标题(例如 \section{...})中使用复杂数学符号(例如 $\mathbb{S}^2$),可能导致编译卡死。

例如

1
\section{Three Spatial Dimensions with Directions on $\mathbb{S}^2$}

编译过程卡死的原因不是标题不支持,而是 PDF 书签不支持,hyperref 宏包会自动生成 PDF 标签。
解决方案:尽量避免在各级标题中使用数学符号;如果必须要在标题中使用复杂数学符号,可以用 hyperref 宏包提供的 \texorpdfstring 命令,原理很简单——给复杂数学公式提供一个简单文本的替代品,hyperref 在生成书签时会使用替代品。

例如

1
\section{Three Spatial Dimensions with Directions on \texorpdfstring{$\mathbb{S}^2$}{S2}}

此时在正文和目录中仍然会显示复杂数学符号,但是在 PDF 书签中会显示更简单的替代文本。

水印与标记

PDF 水印

给PDF文件加上水印是一个常见的需求,比如标记文档的来源,或者标记当前版本为草稿等。

可以使用 draftwatermark 宏包实现

1
\usepackage{draftwatermark}

导入这个宏包就会默认在每一页的中央添加倾斜的水印 DRAFT

测试发现 pdflatex 进行编译可能无法添加水印,或者水印显示异常,因此最好使用 xelatex。

可以调整水印的样式(内容,颜色,大小等)

1
\DraftwatermarkOptions{text={Draft},color={[gray]{0.9}},scale=0.5}

可以在水印中添加编译时间以区别不同版本

1
2
3
4
\usepackage{datetime2}
\usepackage[firstpageonly]{draftwatermark}

\DraftwatermarkOptions{text={Update: \DTMnow},color={[gray]{0.95}},scale=0.3}

也可以在导入宏包时全部作为参数传递

1
2
\usepackage{datetime2}
\usepackage[firstpageonly,text={Update: \DTMnow},color={[gray]{0.95}},scale=0.3]{draftwatermark}

注意:

  • 默认给所有页加水印,导入宏包时可以加参数 firstpage 只在第一页添加水印;
  • 水印的文字只会在页面中央出现一次,不会循环出现;
  • 水印不会遮盖图片,图片会出现在水印的上层。

多人批注标记

在多人协作编辑 LaTeX 文档时,可以给每个人定义一个专属的批注命令,便于区分。

例如

1
2
\newcommand{\li}[1]{{\color{red}{[(Li): #1]}}}
\newcommand{\wang}[1]{{\color{blue}{[(Wang): #1]}}}

在正文中使用 \li{xxx} 即可在添加段内批注。

还可以使用 \marginpar{xxx} 添加页边批注,或者基于下面的 todonotes 宏包进行封装。

待办标记

LaTeX有一个todonotes宏包,可以用来进行一些标注

1
\usepackage{todonotes}

在指定位置使用\todo{xxx}即可在页边加上备注,并且使用横线指向对应位置。
它还提供了一个命令可以生成默认图片在排版中起到占位的效果。

1
\missingfigure{xxx.}

可以给\todo{xxx}或者宏包传递颜色参数,例如

1
2
3
\usepackage[color=green]{todonotes}

\todo[color=green]{xxx}

\todo命令最好直接放在正文中,直接放在一些特殊环境中(例如图表环境的caption,定理环境等)容易导致编译错误,可以加上 inline 选项,例如

1
2
3
4
5
\begin{figure}[htbp]
\centering
\includegraphics{demo.png}
\caption{Title \todo[inline]{xxx.}}
\end{figure}

传递disable选项可以直接全局注释所有的标注

1
\usepackage[disable]{todonotes}

注意:todonotes 的颜色支持依赖 xcolor 包,如果需要在导入 xcolor 包时传递选项,最好在 todonotes 之前手动导入 xcolor 包。

特殊页面需求

护眼色背景

对于非正式的笔记中,我们可以不选择使用标准的白色背景,而是改成护眼色,可以使用 xcolor 宏包的 \pagecolor 命令实现,例如

1
\pagecolor{LimeGreen!15}

还有几种取自 ElegantNote 的护眼色方案可以参考

1
2
3
4
5
6
7
8
\definecolor{geyecolor}{RGB}{199,237,204}
\pagecolor{geyecolor}

\definecolor{geyecolor}{RGB}{251,250,248}
\pagecolor{geyecolor}

\definecolor{geyecolor}{RGB}{250,237,225}
\pagecolor{geyecolor}

特殊页面尺寸

下面是一种适合 iPad 竖屏阅读的页面尺寸

1
2
3
4
5
6
\usepackage{geometry}
\geometry{
paperwidth=7in,
paperheight=10in,
includefoot,
margin=8mm}

下面是一个横版的 A4 纸布局尺寸,可以用于在大屏幕中分享,但又不适合 beamer 的情景

1
2
3
4
5
6
7
8
9
10
\documentclass[17pt]{extarticle}
\geometry{
a4paper,
landscape,
includefoot,
margin=14mm}
\usepackage{setspace}
\setstretch{1.2} % 设置行距
\setlength{\parindent}{0pt} % 取消缩进
\renewcommand{\familydefault}{\sfdefault} % 设置无衬线字体

这里对远距离的屏幕阅读做了一些专门的优化:使用大字体以及支持大字体的 extarticle 文档类,调整行距和缩进,设置无衬线字体。