$\LaTeX{}$ 之自定义$\LaTeX{}$命令和功能(八)
本文主体内容来自一份 (不太) 简短的 LATEX2ε 介绍。
如何制作一个简单但像样的毕业论文/书籍/简历模板,每次可以直接套用,而不是再在导言区写一堆代码?
本章的内容将有助于你实现这一个目标,让你能编写可重复利用的模块——宏包和文档类,并在其中自己定义命令和环境。
不过作为入门手册,这些知识仍是不全面的。如果你不满足于此,需要参考更多资料,比如1 和 2。
自定义命令和环境
定义新命令
使用如下命令可以定义你自己的命令:
\newcommand
的基本用法需要两个必选参数,第一个参数
<name>
是要定义的命令名称(带反斜线),第二个参数
<definition>
是命令的具体定义。方括号里的参数
<num>
是可选的,用于指定新命令所需的参数数目(最多 9
个)。如果缺省可选参数,默认就是 0,也就是新定义的命令不带任何参数。
接下来的两个例子有助于理解。第一个例子定义了一个新的命令
\tnss
。这个命令是本手册英文名称“The Not So Short
Introduction to ”
的简写。如果需要在文档中多次使用本手册的名称,那么使用这个命令是一个非常方便的办法。
第二个例子演示了如何定义一个带参数的命令。在命令的定义中,标记
#1
代表指定的参数。如果想使用多个参数,可以依次使用
#2
、……、#9
等标记。
\(\LaTeX{}\) 不允许使用
\newcommand
定义一个与现有命令重名的命令。如果需要修改命令定义的话,使用
renewcommand
命令。它使用与命令\newcommand
相同的语法。
在某些情况之下,使用 \providecommand
命令是一种比较理想的方案:在命令未定义时,它相当于
newcommand
;在命令已定义时,沿用已有的定义。
定义环境
与 \newcommand
命令类似,可以用
\newenvironment
定义新的环境。它的语法如下所示:
同样地,\newenvironment
命令有一个可选的参数。在
<before>
中的内容将在此环境包含的文本之前处理,而在
<after>
中的内容将在遇到
\end{<name>}
命令时处理。
下面的例子演示了 \newenvironment
命令的用法:
参数 <num>
的使用方式与 \newcommand
命令相同。\(\LaTeX{}\)
还同样保证你不会不小心新建重名的环境。如果你确实希望改变一个现有的环境,你可以使用命令
\renewenvironment
,它使用和命令
\newenvironment
相同的语法。
xparse
宏包简介
通过 \newcommand
和 \newenvironment
定义的命令或环境格式比较固定。如果需要定义带有多个可选参数、或者带星号的命令或环境,可以使用
xparse
宏包,它提供了 \NewDocumentCommand
和
\NewDocumentEnvironment
等命令,具体语法如下:
相比 \newcommand
和
\newenvironment
,xparse
通过{<arg spec>}
来指定参数的个数和格式。基本的参数格式见下表。注意{<arg spec>}
中的空格可以忽略。
不同输入值在解析后的结果可以见 下表 中的示例。
\(\texttt{-NoValue-}\) 标记可以用
\IfNoValueTF
等命令来判断:
举例如下:
\BooleanTrue
和 \BooleanFalse
则可以用
\IfBooleanTF
等命令来判断:
举例如下:
需要注意的是,与命令不同,环境在定义时名字里面可以包含
*
:
1 | \NewDocumentEnvironment {mytabular} { o +m } {...} {...} |
用 s
标记的 *
则应该放在\begin{<env>}
的后面:
1 | \NewDocumentEnvironment { envstar } { s } |
与 \renewcommand
、\providecommand
等命令类似,xparse
宏包也允许在命令或环境已有定义时做出相应的处理,具体见 下表。
编写自己的宏包和文档类
编写简单的宏包
如果定义了很多新的环境和命令,文档的导言区将变得很长,在这种情况下,可以建立一个新的
\(\LaTeX{}\)
宏包来存放所有你自己定义的命令和环境,然后在文档中使用
\usepackage
命令来调用自定义的宏包。
写一个宏包的基本工作就是将原本在文档导言区里很长的内容拷贝到另一个文件中去,这个文件需要以 \(\texttt{.sty}\) 作扩展名。还需要加入一个宏包专用的命令:
这个命令应该放在你的宏包的最前面,并且一定要注意:<package name>
需要和宏包的文件名一致。\ProvidesPackage
是 LaTeX
中用于声明宏包名称和版本信息的命令,通常放在宏包文件的顶部。它的作用是:
- 声明宏包名称:告诉 LaTeX 当前文件的宏包名称。
- 版本信息:提供宏包的版本号和日期,便于用户了解更新情况。
- 避免重复加载:LaTeX 会检查宏包是否已加载,防止重复加载。
1 | \ProvidesPackage{<宏包名称>}[<版本信息>] |
<宏包名称>
:宏包的名字,通常与文件名一致。<版本信息>
:可选,包含版本号和日期。
1 | \ProvidesPackage{mypackage}[2023/10/01 v1.0 My custom package] |
这表示宏包 mypackage
的版本是 1.0,发布于 2023 年 10 月
1 日。
下面的源代码 给出了一个小的宏包示例,其中包含了之前定义的一些命令。
1 | % Demo Package by Tobias Oetiker |
在宏包中调用其它宏包
如果想进一步把各种宏包的功能汇总到一个文件里,而不是在文档的导言区罗列一大堆宏包的话,\(\LaTeX{}\)
允许你在自己编写的宏包中调用其它宏包,命令为
\RequirePackage
,用法和 \usepackage
一致:
编写自己的文档类
当你更进一步,需要编写自己的文档类,如论文模板等,问题就稍稍麻烦了一些。首先,自己的文档类以
\(\texttt{.cls}\) 作扩展名,开头使用
\ProvidesClass
命令:
{<class name>}
也需要和文档类的文件名一致。
但是有了上述命令和和你之前学到的 \newcommand
等,还并不能完成一个文档类的编写,因为诸如
\chapter
、\section
等等许多常用的命令都是在文档类中定义的。事实上,许多时候我们只需要像调用宏包那样调用一个基本的文档类,省去许多不必要的麻烦。在你的文档类中调用其它文档类的命令是
\LoadClass
,用法和 \documentclass
十分相像:
计数器
我们早就见识到了 \(\LaTeX{}\) 对文档元素自动计数的能力:章节符号、列表、图表……它们都是依靠 \(\LaTeX{}\) 提供的计数器功能完成的。
定义和修改计数器
定义一个计数器的方法为:
{<counter name>}
为计数器的名称。计数器可以有上下级的关系,可选参数
[<parent counter name>]
定义为
{<counter name>}
的上级计数器。
以下命令修改计数器的数值,\setcounter
将数值设为
<number>
;\addtocounter
将数值加上
<number>
;\stepcounter
将数值加一,并\(\textbf{将所有下级计数器归零}\)。
计数器的输出格式
计数器<counter>
的输出格式由
\the<counter>
表示。这个值默认以阿拉伯数字形式输出,如果想改成其它形式,需要重定义
\the<counter>
,如将 equation
计数器的格式定义为大写字母:
1 | \renewcommand\theequation{\Alph{equation}} |
命令 \Alph
控制计数器 <counter>
的值以大写字母形式显示。下表列出所有可用于修改计数器格式的命令。注意:这些命令\(\textbf{只能用于计数器,不能直接用于数字}\),如\roman{1}
这样的命令会出错。
计数器的输出格式还可以利用其它字符,甚至其它计数器的输出格式与之组合。如标准文档类里对
\subsection
相关的计数器的输出格式的定义相当于:
1 | \renewcommand\thesubsection{\thesection.\arabic{subsection}} |
\(\LaTeX{}\) 中的计数器
- 所有章节命令
\chapter
、\section
等分别对应计数器 \(\textrm{chapter}\)、\(\textrm{section}\) 等等,而且有上下级的关系。而计数器 part 是独立的。 - 有序列表 \(\texttt{enumerate}\) 的各级计数器为 \(\textrm{enumi}\), \(\textrm{enumii}\), \(\textrm{enumiii}\), \(\textrm{enumiv}\),也有上下级的关系。
- 图表浮动体的计数器就是 \(\textrm{table}\) 和 \(\textrm{figure}\);公式的计数器为 \(\textrm{equation}\)。这些计数器在 \(\textrm{article}\) 文档类中是独立的,而在\(\textrm{report}\) 和\(\textrm{book}\) 中以\(\textrm{chapter}\) 为上级计数器。
- 页码、脚注的计数器分别是 \(\textrm{page}\)和 \(\textrm{footnote}\)。
我们可以利用前面介绍过的命令,修改计数器的样式以达到想要的效果,比如把页码修改成大写罗马数字,左右加横线,或是给脚注加上方括号:
1 | \renewcommand\thepage{--~\Roman{page}~--} |
最后介绍两个有用的计数器:
\(\textbf{secnumdepth}\)
\(\LaTeX{}\) 标准文档类对章节划分了层级:
- 在 \(\textrm{article}\) 文档类里 \(\textrm{part}\) 为 0,\(\textrm{section}\) 为 1,依此类推;
- \(\textrm{report}\) 和 \(\textrm{book}\) 文档类里\(\textrm{part}\)为 -1,\(\textrm{chapter}\) 为 0,\(\textrm{section}\) 为 1,等等。
\(\textrm{secnumdepth}\) 计数器控制章节编号的深度,如果章节的层级大于 \(\textbf{secnumdepth}\),那么章节的标题、在目录和页眉页脚的标题都不编号(照常生成目录和页眉页脚),章节计数器也不计数。
可以用 \setcounter
命令设置 \(\textrm{secnumdepth}\)
为较大的数使得层级比较深的章节也编号,如设置为 4 令
\paragraph
也编号;或者设置一个较小的数以取消编号,如设置为
-1 令 \chapter
不编号。后者是生成不编号的章节的一个妙招,免去了手动使用
\addcontentsline
和 \markboth
的麻烦。
\(\textrm{secnumdepth}\) 计数器在 \(\textrm{article}\) 文档类里默认为 3(\(\textrm{subsubsection}\) 一级);在 \(\textrm{report}\) 和 \(\textrm{book}\) 文档类里默认为 2(\(\textrm{subsection}\) 一级)。
\(\textbf{tocdepth}\)
\(\texttt{tocdepth}\) 计数器控制目录的深度,如果章节的层级大于 \(\texttt{tocdepth}\),那么章节将不会自动写入目录项。默认值同 \(\textrm{secnumdepth}\)。
\(LaTeX{}\) 可定制的一些命令和参数
\(\LaTeX{}\) 事实上有相当一些可以定制的命令和参数,不过对于修改样式或者开发宏包来说,这些定制项还远远不够。
对于用户来讲,容易定制的是这一些项目:
标题名称/前后缀等。 下表 列出了标准文档类里可定制的项目, 表中所有的 \(\LaTeX{}\) 命令都可以用
\renewcommand
来修改。长度。前文在叙述各种排版元素时已经介绍过一些,现归纳于 下表。表中所有的长度命令可用
\setlength
来修改。
参考文献
[1] Partl H, Hyna I, Schlegl E. 一份 (不太) 简短的 LATEX2ε 介绍[J].
2024. https://github.com/CTeX-org/lshort-zh-cn
Frank Mittelbach, Michel Goossens, Johannes Braams, David Carlisle, Chris Rowley. The LATEX Companion, 2nd edition. Addison-Wesley, Reading, Massachusetts, 2004, ISBN 0-201-36299-6.↩︎
LATEX Project Team. LATEX 2 for class and package writers.
CTAN://macros/latex/base/clsguide.pdf
(texdoc clsguide)↩︎