跳转到内容

LaTeX/Plain TeX

来自维基教科书,开放的书籍,开放的世界

LaTeX

入门
  1. 简介
  2. 安装
  3. 安装额外的软件包
  4. 基础
  5. 如何获取帮助

常用元素

  1. 文档结构
  2. 文本格式
  3. 段落格式
  4. 颜色
  5. 字体
  6. 列表结构
  7. 特殊字符
  8. 国际化
  9. 旋转
  10. 表格
  11. 标题创建
  12. 页面布局
  13. 自定义页面页眉和页脚‎
  14. 导入图形
  15. 浮动体、图形和标题
  16. 脚注和边注
  17. 超链接
  18. 标签和交叉引用
  19. 首字母

机制

  1. 错误和警告
  2. 长度
  3. 计数器
  4. 盒子
  5. 规则和撑杆

技术文本

  1. 数学
  2. 高级数学
  3. 定理
  4. 化学图形
  5. 算法
  6. 源代码清单
  7. 语言学

特殊页面

  1. 索引
  2. 术语表
  3. 参考文献管理
  4. 更多参考文献

特殊文档

  1. 科学报告(学士报告、硕士论文、博士论文)
  2. 信件
  3. 演示文稿
  4. 教师专区
  5. 简历
  6. 学术期刊(MLA、APA 等)

创建图形

  1. 介绍过程化图形
  2. MetaPost
  3. Picture
  4. PGF/TikZ
  5. PSTricks
  6. Xy-pic
  7. 创建 3D 图形

编程

  1. Plain TeX
  2. 创建软件包
  3. 创建软件包文档
  4. 主题

其他

  1. 模块化文档
  2. 协作编写 LaTeX 文档
  3. 导出到其他格式

帮助和建议

  1. 常见问题解答
  2. 技巧和窍门

附录

  1. 作者
  2. 链接
  3. 软件包参考
  4. LaTeX 文档示例
  5. 索引
  6. 命令词汇表

编辑此框编辑目录


当你使用 LaTeX 宏时,你会发现它非常有限。你可能会好奇,你每天使用的所有这些软件包是如何用这么少的代码实现的。事实上,LaTeX 是一套 Plain TeX 宏,大多数软件包都使用 Plain TeX 代码。Plain TeX 的级别要低得多,它有更多功能,但学习曲线陡峭,编程复杂。

除了少数例外,你可以在有效的 LaTeX 文档中使用完整的 Plain TeX 语言,反之则不然。

词汇

[edit | edit source]

为了避免混淆,有必要解释一些术语。

  • 一个 是在开括号之后和匹配的闭括号之前的任何内容。
  • 一个 标记 是一个字符、一个控制序列或一个组。
  • 一个 控制序列 是任何以 \ 开头的内容。它不会按原样打印,而是根据其类型由 TeX 引擎进行扩展。
  • 一个 命令 (或 函数)是一个控制序列,它可能扩展为文本,控制序列的(重新)定义等。
  • 一个 原语 是一个在 TeX 引擎中硬编码的命令, 它不是用 Plain TeX 编写的。
  • 一个 寄存器 是 TeX 处理变量的方式。它们的数量是有限的(在经典 TeX 中,每种类型的寄存器有 256 个,在 e-TeX 中有 32767 个)。
  • 一个 长度 是一个包含长度的控制序列(一个数字后跟一个单位)。参见 长度
  • 一个 字体 是一个引用字体文件的控制序列。参见 字体
  • 一个 盒子 是一个用于打印的对象。出现在纸张上的任何内容都是一个盒子:字母、段落、页面......参见 盒子
  • 一个 胶水 是一个特定的空间量,当盒子被连接在一起时,它被放置在盒子之间。
  • 一个 计数器 是一个包含数字的寄存器。参见 计数器

可能还有更多的术语,但我们希望现在已经足够了。

类别码

[edit | edit source]

在 TeX 中,一些字符具有特殊的含义,而不是打印相关的字形。例如,\ 用于引入控制序列,默认情况下不会打印反斜杠。

为了区分字符的不同含义,TeX 将它们分成 类别码,简称 类别码。TeX 中有 16 种类别码。

TeX 的一项强大功能是它能够重新定义语言本身,因为有一个 \catcode 函数,它可以让你更改任何字符的类别码。

然而,不建议这样做,因为它会使代码难以阅读。如果你在一个类或样式文件中重新定义了任何类别码,请确保在文件末尾将其恢复。

如果你在文档中重新定义了类别码,请确保在序言之后进行,以防止与软件包加载冲突。

代码 描述 默认集
0 转义字符和控制序列 \
1 组的开始 {
2 组的结束 }
3 数学转移 $
4 对齐制表符 &
5 行尾 ^^M (ASCII 回车)
6 宏参数 #
7 上标 ^^^K
8 下标 _^^A
9 忽略字符 ^^@ (ASCII 空字符)
10 空格 ^^I (ASCII 水平制表符)
11 字母 A...Za...z
12 其他字符 不在其他类别码中列出的所有内容。最值得注意的是 @。
13 活动字符 ~^^L (ASCII 换页符)
14 注释字符 %
15 无效字符 ^^? (ASCII 删除)

活动字符

[edit | edit source]

活动字符类似于宏:它们是单个字符,将在任何其他命令之前进行扩展。

\catcode`| = 13
\def|{\TeX}
...
This is a stupid example of |.

这是一个关于 TeX 的愚蠢示例。

请注意,活动字符需要直接后跟定义,否则编译将失败。

示例

[edit | edit source]
Texinfo

Texinfo 使用类似于 TeX 的语法,但有一个主要区别:所有函数都以 @ 而不是 \ 开头。这并非偶然:它实际上使用 TeX 打印文件的 PDF 版本。它基本做的就是输入texinfo.tex它重新定义了控制序列字符。可能的实现

\catcode`\@=0
@def@@{@char64} % To write '@' character.
\catcode`\\=13 @def\{{@tt @char92}}

The @TeX command was previously written '\TeX'. It is now written '@@TeX'.

TeX 命令以前写成 '\TeX'。现在写成 '@TeX'。

通过这种重新定义,'@' 现在应该引入每个命令,而 '\' 实际上将打印一个反斜杠字符。

项目符号

有些人可能发现 LaTeX 列表环境的语法有点繁琐。这里有一个快速定义类似维基的项目符号的方法

\catcode`| = 13
\def|{\item {--}}
\def\itemize#1{{\leftskip = 40 pt #1 \par}}

\itemize{
| First item
| Second item
}
美元符号和数学

如果您要打印很多“美元”符号,您可能最好更改数学移位字符。

\catcode`$ = 11
\catcode`| = 3

It costs $100.
Let's do the math: |50+50=100|. Let's highlight it:
||50+50=100||

\makeatletter\makeatother

[edit | edit source]

如果您进行了一些 LaTeX 编程,您一定遇到过这两个命令,\makeatletter\makeatother

在 TeX 中,'@' 字符默认属于类别码 11 字母。这意味着您可以将其用于宏名称。LaTeX 利用类别码来指定规则:所有非公共的、内部的宏,其名称中至少包含一个 '@' 字符,这些宏不应由最终用户访问。在文档中,LaTeX 将 '@' 的类别码更改为 12,即 其他

这就是为什么当您需要访问 LaTeX 内部函数时,必须将所有访问私有函数的命令括在 \makeatletter\makeatother 之间。它们所做的只是更改类别码

\def\makeatletter{\catcode`@ = 11}
\def\makeatother{\catcode`@ = 12}

普通 TeX 宏

[edit | edit source]

\newcommand\renewcommand 是 LaTeX 特定的控制序列。它们检查没有现有的命令被新定义所覆盖。

在普通 TeX 中,用于宏定义的原语不会对可能的覆盖进行检查。您需要确保没有破坏任何东西。

语法是

\def<macroname>#1<sep1>#2<sep2>{macro content, use of argument #1, blah, #2 ...}

您可以在参数之间使用(几乎)任何字符序列。例如,让我们编写一个简单的宏,它将小数点分隔符从点更改为逗号。首先尝试

\def\pointtocomma #1.#2{(#1,#2)}
%%...

\pointtocomma 123.456

这将打印 (123,4)56。我们添加了括号只是为了突出显示这里的问题。每个参数是最短的可能的输入序列,与宏定义匹配,包括分隔符。因此 #1 匹配直到第一个点的所有字符,而 #2 仅匹配第一个标记, 第一个字符,因为它之后没有分隔符。

解决方案:添加第二个分隔符。空格可能看起来很方便

\def\pointtocomma #1.#2 {(#1,#2)}

一般来说,每当您希望使用特定分隔符获得多个参数时,都要考虑最后一个分隔符。如果您不想使用分隔符,那么普通 TeX 宏的使用方式与 LaTeX 宏相同(没有默认参数)

\def\mymacro#1#2#3{{\bf #1}#2{\bf #3}}
%% ...
\mymacro{word1}{word2 word3}{!!!}

扩展定义

[edit | edit source]

TeX 还有另一个定义命令:\edef,它代表 扩展定义。语法保持不变

\edef<macroname><argumentslist>{<expanded content>}

内容在使用 \edef 的地方被扩展(但不会执行, 打印),而不是在定义的宏被使用的地方。宏扩展并不总是显而易见的...

示例

\def\intro{Example}
\edef\example#1{\intro~---~#1}
\def\intro{Exercise}

\example{This is an example}

这里 \intro 的重新定义对 \example 不会有任何影响。

全局定义

[edit | edit source]

定义仅限于其范围。但是,有时将宏定义在一个组中,使其在该组之外以及直到文档结束时仍然有效,这可能很方便。这就是我们所说的 全局定义

{
\def\LocalTeX{Local\TeX}
\global\def\GlobalTeX{Global\TeX}
}
I can still access the \GlobalTeX{} macro here.

您也可以将 \global 命令与 \gdef 结合使用。

这两个命令都有快捷方式

  • \gdef 用于 \global\def
  • \xdef 用于 \global\edef

长定义

[edit | edit source]

之前的定义命令不允许您在多个段落中使用它们, 包含 \par 命令(或双行换行符)的文本。

您可以在定义之前加上 \long 命令,以允许使用多段落参数。

示例

\long\def\dummy#1{#1}
\dummy{First paragraph\par Second paragraph}

外部定义

[edit | edit source]

此前缀宏阻止定义在某些上下文中使用。它有助于合并宏并使其因错误的上下文而更不容易出错。外部宏 旨在在任何上下文之外使用,因此得名。

例如,以下代码将失败

\outer\def\test{a test}
\def\failure{\test}

外部宏不允许出现在

  • 宏参数
  • 跳过的条件
  • ...

letfuturelet

[edit | edit source]

\let<csname><token>\expandafter\def\expandafter<csname>\expandafter{<content>} 相同。它定义了一个新的控制序列名称,该名称等效于指定的 token。该 token 通常是另一个控制序列。

请注意,\let 只会扩展 token 一次,这与 \edef 相反,\edef 将递归扩展,直到不再可能进一步扩展。

示例[1]

Using let:\par
\def\txt{a}
\def\foo{\txt}
\let\bar\foo
\bar % Prints a
\def\txt{b}
\bar % Prints b

Using edef:\par
\def\txt{a}
\def\foo{\txt}
\edef\bar{\foo}
\bar % Prints a
\def\txt{b}
\bar % Prints a

\futurelet<csname><token1><token2>... 的工作方式略有不同。首先,token2 被分配给 csname,然后 TeX 处理 <token1><token2>... 序列。因此,\futurelet 允许您在使用标记后立即分配它。

特殊的控制序列名称

[edit | edit source]

某些宏的名称可能无法直接写入。对于由宏名称组成的宏名称,情况就是如此。示例

\def\status{full}
\def\varempty{This is empty}
\def\varfull{This is full}

\csname var\status \endcsname

最后一行将根据 \status 打印一个句子。

此命令实际上与 \string 相反,\string 会打印一个控制序列名称,而不会扩展它

{\tt \string\TeX}

\TeX

控制扩展

[edit | edit source]

\expandafter{token1}{token2} 将在 token1 之前扩展 token2。当需要扩展 token2 但由于 token1 而无法扩展时,这有时是必需的。

{\tt \expandafter\string\csname TeX\endcsname}

\TeX

\noexpand 有助于对 \edef 中扩展的内容进行细粒度控制。示例

\def\intro{Example}
\def\separator{~---~}
\edef\example#1{\intro\noexpand\separator#1} 

\example{no expand makes the separator dynamic in an {\tt \string\edef}.}

\def\intro{For instance}
\def\separator{~:~}

\example{the separator changed, but not the first word.}


\the 控制序列可以让你看到各种 TeX 类型的內容

  • 类别码
  • 字符定义
  • 字体参数
  • 内部参数
  • 长度
  • 寄存器
  • ...

示例

Text dimensions: $ \the\hsize \times \the\vsize $

寄存器

[编辑 | 编辑源代码]

寄存器是一种类型的变量。它们的數量有限,从 0 到 255。共有 6 种不同的类型

类型 描述
盒子 一个盒子
计数器 一个整数
尺寸 一个长度
粘性 (mu 单位) 一个粘性 (mu 单位)
粘性 一个粘性
令牌 一个令牌序列

TeX 在内部使用一些寄存器,所以最好不要使用它们。

保留寄存器列表

  • \box255 用于页面的内容
  • \count0-\count9 用于页码编号

临时寄存器(可自由使用)

  • \box0-\box254
  • \count255
  • \dimen0-\dimen9
  • \muskip0-\muskip9
  • \skip0-\skip9

使用 '=' 控制字符分配寄存器。对于盒子寄存器,请使用 \setbox 命令。

\count255=17
\setbox\mybox=\hbox{blah}

你可以使用以下保留宏之一来防止任何冲突

\newbox
\newcount
\newdimen
\newmuskip
\newskip
\newtoks

这些宏使用以下语法:\new*<csname>。例如

\newbox\mybox
\setbox\mybox=\hbox{blah}

这些命令不能在宏内部使用,否则每次调用宏都会保留另一个寄存器。

你可以使用 \the 命令打印寄存器。对于计数器,请改用 \number 命令。对于盒子,请使用 \box 命令。

\the\hsize
\number\count255
\box\mybox

TeX 的算术功能非常有限,虽然这个基础足以扩展到一些有趣的功能。三个主要功能

\advance <register> by <number>
\multiply <register> by <number>
\divide <register> by <number>

register 可以是计数器、尺寸、粘性 (mu 单位) 或粘性类型。它对盒子和令牌没有意义。

条件语句

[编辑 | 编辑源代码]

基本语法是

\if* <test><true action>\fi
\if* <test><true action>\else<false action>\fi

其中 \if* 是以下命令之一。

控制序列 描述
\if <a><b> 如果两个字符码相等,则为真。
\ifcat <a><b> 如果两个类别码相等,则为真。
\ifdim <a><rel><b> 尺寸关系,要么<, >要么=.
\ifeof 如果文件末尾或不存在的文件,则为真。
\iffalse 始终为假。
\ifhbox <reg> 如果盒子寄存器包含水平盒子,则为真。
\ifhmode 如果在水平模式下,则为真。
\ifinner 如果在内部模式下,则为真。
\ifmmode 如果在数学模式下,则为真。
\ifnum <a><rel><b> 数字关系,要么<, >要么=.
\ifodd <num> 如果数字是奇数,则为真。
\iftrue 始终为真。
\ifvbox <reg> 如果盒子寄存器包含垂直盒子,则为真。
\ifvmode 如果在垂直模式下,则为真。
\ifvoid <reg> 如果盒子寄存器为空,则为真。
\ifx <a><b> 如果两个宏展开为相同,或者如果两个字符码相等,或者如果两个类别码相等,则为真。

示例

\ifnum 5>6
This is true
\else
This is false
\fi

这是假的


自定义条件语句

[编辑 | 编辑源代码]

你可以使用 \newif 命令创建新的条件语句(作为一种 布尔变量)。通过这些自定义条件语句,你可以以一种优雅的方式控制代码的输出。说明条件语句用法的最佳方法是通过一个例子。

必须生成两个版本的文档。一个版本是针对 A 组的,另一个版本是针对其他所有人的(即不属于 A 组的)。

1. 我们使用 \newif 来定义我们的条件语句(即布尔变量)。

\newif\ifgroupA

2. 以下方式为我们的条件语句设置一个值(真或假)

\groupAtrue % or
\groupAfalse

也就是说

\<conditionalsname>true
\<conditionalsname>false

取决于我们希望在条件语句中设置哪个值。

3. 现在我们可以在之后的任何地方使用我们的条件语句,在 if 控制结构 中。

\ifgroupA
  % Here we write the code of the document that is
  % intended for the group A
\else
  % Here we write the code of the document that is 
  % intended for the rest of the people
\fi

一个完整的例子是

\newif\ifdirector 

%I set the conditional to false
\directorfalse

\ifdirector
 I write something for the director.
\else
 I write something for common people.
\fi

我写一些针对普通人的东西。

情况语句

[编辑 | 编辑源代码]

语法是 \ifcase <number><case0>\or<case1>\or...\else<defaultcase>\fi。如果 number 等于情况编号,它的内容将被打印。注意,它从 0 开始。

\ifcase 2 a\or b\or c\or d\else e\fi

c

\else 用于指定默认情况(当之前的任何情况都没有匹配时)。

基本语法是

\loop <content> \if*<condition><true action>\repeat

与往常一样,contenttrue action 是任意的 TeX 内容。\if* 指的是任何 条件语句。注意,没有 false action,你不能在 \if*\repeat 之间放置 \else。在某些情况下,这将与你想要的结果相反;你需要更改条件或使用 \newif 定义一个新的条件语句。例如

\count255 = 1
\loop
  \TeX
\ifnum\count255 < 10
\advance\count255 by 1
\repeat

上面的代码将打印十次 TeX。

什么都不做

[编辑 | 编辑源代码]

有时,告诉 TeX 你什么都不想做可能很有用。有两个命令可以做到这一点:\relax\empty

经典示例

\def\myspace{\hskip 25pt\relax}
\myspace{} plus 10pt

如果在命令之后遇到 plusminus\relax 将阻止出现不希望的行为。

\empty\relax 之间的区别在于展开:\empty 在宏展开后会消失。

TeX 字符

[编辑 | 编辑源代码]

我们可以使用 \char {charcode} 命令打印所有字符。charcode 实际上是字节值。例如

\char65 = \char `A = \char `\A

大多数字符对应于 ASCII 值(例如 A-Za-z),一些字符替换了 ASCII 中的不可打印字符。

chardefmathchardef

[编辑 | 编辑源代码]

你可以定义控制序列以展开为特定字符。语法是 \chardef<control sequence>=<charcode>。以下序列执行相同的操作。

\chardef\myA=65
\chardef\myA=`A
\chardef\myA=`\A

示例

\mathchardef\alphachar = "010B
$\alphachar$

字体编码映射

[编辑 | 编辑源代码]

我们可以使用上面的原语打印字体编码映射。

\count255 = 0
\loop
  [\number\count255 =\char\number\count255]
\ifnum\count255 < 127
\advance\count255 by 1
\repeat

另一个版本,使用不同的字体,每行一个条目

\count255 = 0
\loop
  [\number\count255 =
    \char\number\count255 \ 
    {\tt \char\number\count255}
    {\it \char\number\count255}
  ]
  \hfil\break
\ifnum\count255 < 127
\advance\count255 by 1
\repeat

逐字文本行和空格

[编辑 | 编辑源代码]

发现 (La)TeX 将所有空白都视为同一类型的间距粘性,这一点令人困惑。Plain TeX 提供了一些命令来保留你写的间距和换行符

\begingroup
\obeylines
\obeyspaces
Relevant text here
\endgroup

这意味着你可能需要组合自己的逐字文本环境和你的命令

\newenvironment{myverbatim}{\begingroup \obeylines \obeyspaces}{\endgroup}
\newcommand{\mycommand}[n]{do something with #1 .. #n}

然后在你的 tex 文件中

\begin{myverbatim}
\mycommand{
whichever text it is important you
preserve the spacing and newslines
for, like when you want to generate
a verbatim block later on.
}
\end{myverbatim}

定义宏的宏

[编辑 | 编辑源代码]

在某些情况下这很有用,例如定义语言命令,如多语言版本中所述,终端用户可以编写

\en{some english text}
\de{etwas deutscher Text}

并确保它切换到相应的 Babel 语言。

让我们定义一个宏,它将定义语言命令,例如。这些命令很简单:如果参数是\locale变量的值,则相应的宏直接打印其内容。否则,它什么也不做。

基本上,我们想做的事情非常简单:定义一堆像这样的宏

\newcommand{\de}[1]{#1}
\newcommand{\en}[1]{}
\newcommand{\fr}[1]{}

在前面的代码片段中,只有\de命令将输出其内容,\en\fr将什么也不打印。这就是我们想要的。当您想自动化任务时,或者您有许多语言,并且想要更改语言选择时,问题就出现了。您只需要移动#1,但这很不方便,而且无法从命令行选择 Babel 语言。仔细考虑一下...

我们将做的是根据\locale变量的值(或您选择的任何变量)动态定义语言命令。因此使用来自ifthen包的\equal命令。

由于用 LaTeX 几乎不可能写出来,我们将使用一些 Plain TeX。

\def\locale{de}

\def\localedef#1{
  \ifthenelse{ \equal{\locale}{#1} }{
    %% Set the Babel language.
    %% Define the command to print the content.
  }{
    %% Define the command to print nothing.
  }
}

另一个问题出现了:如何定义一个名称是变量的命令?在大多数编程语言中,这根本不可能。我们可以尝试写的是

\def\#1 #1{#1}

它将因两个原因而失败。

  1. 最后两个“#1”应该指的是宏的参数,但它们首先扩展到\localedef宏的第一个参数,因为它们在该宏的正文中。
  2. \#1扩展为两个标记:“#”和“1”,而\def命令将失败,因为它需要有效的控制序列名称。

问题 1 的解决方案很简单:使用“##1”,它将在宏执行时扩展为“#1”。

对于问题 2,它有点棘手。有可能告诉tex某个特定标记是控制序列。这就是\csname...\endcsname的用途。但是

\def\csname#1\endcsname ##1{##1}

将失败,因为它将重新定义\csname为“#1”,这不是我们想要的,那么tex将遇到\endcsname,这将导致错误。

我们需要延迟\def的扩展,告诉tex首先扩展\csname内容,然后对其应用\def。有一个命令可以做到这一点:\expandafter{token1}{token2}。它将在{token1}之前扩展{token2}

最后,如果我们想从命令行设置语言,我们必须能够设置\locale变量,以便源代码中的变量是默认值,可以被命令行中的变量覆盖。这可以通过\providecommand来实现

\providecommand\locale{fr}

最终代码是

%% Required package.
\usepackage{ifthen}
 
%% TeX function that generates the language commands.
\def\localedef#1#2{
  \ifthenelse{ \equal{\locale}{#1} }{
    \selectlanguage{#2}
    \expandafter\def\csname#1\endcsname ##1{##1}
  }{
    \expandafter\def\csname#1\endcsname ##1{}
  }
}
 
%% Selected language. Can be placed anywhere before the language commands.
\providecommand\locale{fr}
 
%% Language commands.
\localedef{de}{ngerman}
\localedef{en}{english}
\localedef{fr}{frenchb}
%% ...

您可以使用以下命令进行编译

latex '\providecommand\locale{en}\input{mydocument.tex}'

笔记和参考资料

[编辑 | 编辑源代码]
  1. 来自 tex.stackexchange.com: \let 和 \edef 之间的区别是什么?
进一步阅读


上一节:宏 索引 下一节:创建包
华夏公益教科书