> For the complete documentation index, see [llms.txt](https://overleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://overleaf-pro.ayaka.space/latex/zh-cn/shen-ru-wen-zhang/23-how-does-latex-typeset-headers-and-footers.md).

# LaTeX 如何排版页眉和页脚？

## LaTeX 如何排版页眉和页脚？

### 引言

如果你正在寻找更改页眉和页脚的操作指南，请 [访问我们的帮助页面](/latex/zh-cn/ge-shi-hua/02-headers-and-footers.md) 因为本文重点解释 TeX 引擎以及 LaTeX 用于生成页眉和页脚的底层机制。

* **注（更新）**：本文探讨的是 2022 年 6 月之前使用的 LaTeX 核心标记机制，当时引入了一个新的、更加灵活的 LaTeX 标记处理实现。参见 [LaTeX 新标记机制概览](https://www.latex-project.org/help/documentation/ltmarks-doc.pdf) 了解更多详情。出于向后兼容的原因，LaTeX 仍然支持其旧版标记机制。讨论 TeX 引擎行为的文章内容不受 LaTeX 这些变化的影响。
* **注意**：在这些讨论中，“LaTeX”、“TeX”和“TeX 引擎”这几个术语会反复出现，因此了解它们的含义很有帮助——请参见 Overleaf 文章 [名称中有什么：TeX 的多种变体指南](/latex/zh-cn/shen-ru-wen-zhang/55-what-s-in-a-name-a-guide-to-the-many-flavours-of-tex.md) 以获取更多信息。

为了提供有用的背景/上下文，我们先概述一些与基于 TeX 的排版相关的底层领域——包括临时（内部）内容存储、分页断行与页面构建。为了简化讨论，我们考虑一个已排版页面，它包含正文以及一些页眉和页脚。诸如图（浮动体）和脚注之类的页面项目未予讨论，因为它们涉及一种称为 *插入项*，这远远超出了本文的范围。

我们先从……的基本概念开始 *节点*：TeX 引擎用于临时（内部）内容存储的基本构件。

### 关于内容存储的一些说明：TeX 节点

为了存储已排版内容，TeX 引擎使用一系列所谓的 *节点*，你可以把它们看作大小可变的计算机（设备）内存“块”，彼此链接形成一个列表（参见这篇 [维基百科上关于链式数据结构的文章](https://en.wikipedia.org/wiki/Linked_data_structure)).

![TeX 引擎如何使用链接节点存储内容的示意图](/files/c4208434f06494ee13c41907b87d735897276a59)

为了表示基于 TeX 的排版的基本元素——例如字符、粘连、盒子、惩罚、间距调整以及 [*标记*](#why-were-here-understanding-mark-nodes) （仅举几例）——TeX 引擎使用不同的 *类型* 节点类型。某些节点，例如盒子节点，需要存储的数据比字符节点等更简单的节点类型更多；因此，有些节点比其他节点更大——在内存中存储它们需要更多字节。阅读 TeX 资料时，你可能会遇到诸如 *字符节点*, *粘连节点*, [*标记节点*](#why-were-here-understanding-mark-nodes) 等等这样的术语：现在你知道，它们不过是用于存储表示 TeX 排版元素数据的内存块的名称。

#### 我们为何在此：理解标记节点

在众多节点类型中，我们需要讨论的是一种称为 *标记节点* 的节点，它由 `\mark` 命令设置为特定值：

```latex
\mark{要存储的内容}
```

该 `\mark` 命令不会直接产生任何排版内容：它的参数 `*要存储的内容*` 会被保存到内存中供以后使用，并且会创建一个 *标记节点* 用于包含以下内容的内存位置 *要存储的内容*。正如我们将看到的， `\mark` 命令的创建 *正是为了* 帮助生成页眉和页脚。

由 `\mark` 命令创建的标记节点会“嵌入”到页面内容中，供页面构成的最后阶段使用；此时 TeX 引擎已经准备好为已排版页面（正文）添加页眉和页脚。通常， `*要存储的内容*` 其中会包含页码以及章节或节标题/编号等内容——这些就是页眉和页脚的构成要素。

#### ε-TeX 添加了 \marks 命令

[ε-TeX 第 2 版](https://texdoc.org/serve/e-TeX/0)，作为 Knuth 原始 TeX 的一个扩展，于 1998 年发布，并提供了新功能以及对现有功能的增强。如今，ε-TeX 的大多数增强功能已被主流 TeX 引擎采纳：pdfTeX、LuaTeX 和 XeTeX——参见 [**`-etex`** TeX 引擎的命令行选项](/latex/zh-cn/geng-duo-zhu-ti/44-tex-engine-command-line-options-for-pdftex-xetex-and-luatex.md) ，它可启用 ε-TeX 扩展。

Knuth 最初的 TeX 软件提供了 \mark 命令，它只创建 1 种“类别”或“类型”的标记节点。你的文档中可以包含许多 \mark 命令，但它们都会创建同一“类型”或“类别”的标记节点——没有内置机制对它们进行分组或分类。ε-TeX 的一项增强移除了这一限制：pdfTeX、LuaTeX 和 XeTeX 支持的 \marks 命令：

```latex
\marks n {要存储的内容}
```

其中 `n` 是一个整数（`0` <= `n` < `32768`），它决定标记节点的 *类别*.

注意：

* 写出 `\marks 0` （即， `n`=`0`）与使用 TeX 原始的 `\mark` 命令
* ε-TeX 还引入了标记变量（[后文见](#pre-output-checks-looking-for-mark-nodes)）用于每个类别（`n`）的标记： `\firstmarks n`, `\topmarks n` 和 `\botmarks n`。选定类别的标记变量可用于为特定排版任务提供标记数据。
* LuaTeX 提高了 `n` 更改为 `65535`的最大值，从而使 $$2^{16}$$ 标记类别

### TeX 使用标记的原因

要理解 *为什么* （以及如何）TeX 引擎使用 `\mark` （或者 `\marks`）命令来生成页眉和页脚，我们需要了解 TeX 引擎构建页面的一些“怪癖”——即寻找分页点并将完成的页面输出到 PDF 文件中。实际上，这些页面构建过程很复杂，但核心原理/概念可以简化，以提供足够的背景说明，帮助我们理解标记。

当 TeX 引擎完成诸如段落、表格或数学公式等单个内容项的排版时，它会调用一个内部例程（一个名为 `build_page(...)`）该例程会尝试将这些新排版的项目添加到当前正在内存中构建的页面中。将这些新材料添加到正在构建的页面时，TeX 还会检查新加入的内容是否已使当前页面“足够满”。如果页面已满，TeX 就可以创建一个 *分页点*——将该页内容发送进行最终处理（“打包”），并随后输出到 PDF 文件中。

页面打包（排版）最后阶段的一个关键特征是所谓的 *输出例程* ，它实际上是一个用户定义的命令序列，用于打包页面内容，使其准备好发送到 PDF 文件中。“打包”的一个示例操作是添加页眉和页脚，但输出例程的用法有很多——例如，参见 [TUGboat 中的这篇文章](https://tug.org/TUGboat/tb11-1/tb27salomon.pdf)。输出例程是 TeX 中一个相当复杂的领域，因此这里我们不展开细讲：就目前而言，只需记住，页面构成的最后阶段会使用某种用户定义的命令序列（统称为一个 *输出例程*).

但是，让我们考虑这样一种情况：当 TeX 完成一段较长文本的排版并将其断成一系列单独的排版行时，可能会发生什么。TeX 会想把这段新排版的段落逐行添加到当前正在构建的页面中，但也许只有 *一些* 这些行中的一部分能够放入当前页面。注意，这里所说的“页面”指的是主要文本区域（页面正文），它 *不会* 不包括其他页面元素，例如页眉和页脚，它们会稍后（由输出例程）添加。

在若干段落行被添加到当前页面后，它可能会变得“足够满”，这时 TeX 就该创建分页点了。不过，我们还会有一些“剩余”内容，因为只有 *part* 的段落内容进入了当前页面：“多余的段落内容”会被保存起来，留待以后创建 *下一* 页。下图说明了这个概念：段落的一部分在 *当前* 页上，另一部分则保留到 *下一* 页。

![跨越分页点的段落示意图](/files/fe969fee160aaf6a1f7aef838a43a875f8bfe715)

这个段落示例表明，TeX 引擎往往会处理（排版）某些内容片段，即使它们 *整体上*，也无法完整地放入当前页面——因为这些内容超出了必须分页的位置。请记住，TeX 引擎是通过先输入再排版 *整个段落* 来寻找断行的，而且不像某些应用程序那样逐行排版段落。换句话说，TeX 排版的是 *整个* 段落，无论它是否适合当前页面（这会稍后计算）。顺便说一句，“先完整排版再加入当前页面”这一原则同样适用于其他类型的 TeX 内容，包括表格；表格会先从 .tex 文件完整读入，并在尝试加入当前页面之前整体排版。

对于我们的段落示例，该段落的完整文本， *包括其中的任何命令*，都已被完全处理——这意味着 TeX 的排版已经到达你 `.tex` 文件中的某个位置。TeX 引擎的“当前内部状态”，例如变量的值或已存储的参数设置，将反映截至当前位置——也就是段落末尾——所处理的内容，包括其中的任何命令。

虽然整个段落已经排版完成，但其中一部分无法容纳在当前页面中，因此“多余的段落内容”会被保存以供 *下一* 页使用。在生成“多余的段落内容”的 LaTeX 代码（文本加命令）中，TeX 引擎 *可能* 调用了任意数量的命令和宏，这些命令和宏改变了 TeX 引擎内部存储的值和变量——也就是对 TeX 的“当前状态”进行了更改。

下图说明了 TeX 一个极其重要的“怪癖”，它会影响基于 TeX 的排版的多个方面。看图的右侧，你会看到表示当前存储在内存中的所有内容的节点：浅灰色区域包含构成 *当前* 页的节点，而深灰色区域是预定送往 *下一* 页的排版内容。这个场景反映了我们的段落示例：深灰色区域代表“多余的段落内容”，即因无法放入当前页面而被保留的文本行。

![TeX 找到分页点时的节点位置示意图，同时显示为下一页保留的内容](/files/a0c548decff664e4345e1ca943880d15a96ace1c)

请记住，一旦 TeX 获得了一个“足够满”的页面（正文），它就会把该内容交给一个称为 *输出例程* 的东西，它是一个用户定义的命令序列。除其他事项外，输出例程会向页面正文添加页眉和页脚，从而完成页面的定稿，准备将其输出到 PDF 文件中。

上图表示 TeX 选择在某个特定节点处分页的实例，例如 `\baselineskip` 排版行之间的粘连，并且已经准备好使用输出例程——一组内置命令和用户定义宏——开始打包当前页面。 *然而*，除了构成当前页面（存储在内存中）的内容之外，TeX 还已经处理并存储了（在内存中） *额外内容*，来自你的 `.tex` 文件。那些额外内容延伸 *到了分页点之后* 和 *可能* 可能包含了会更改重要页面相关值或变量的命令，从而影响 TeX 的“当前内部状态”——其中包括内部参数的值或宏存储的数据。

当 TeX 开始打包当前页面时，它的“当前内部状态”是 *不* 由分页点之前排版的所有内容所决定，因为 TeX 已经 *越过了那个点* 并在你的文件中继续处理了额外内容。如果你把文件中对应分页发生的位置，以及 TeX 实际在你的 `.tex` 文件中实际到达的位置进行比较，那么 TeX 的两个对应内部状态——包括存储的数据和变量的值——可能会非常不同。TeX 排版的当前状态通常领先于分页发生的位置；也就是说，TeX 的排版活动 *并不同步* 于通过输出例程输出已排版页面的过程。

#### 假设示例：如何产生错误的页眉

下图说明了假设的命令 `\myheader` 和 `\myfooter` 用于存储所需的页眉和页脚文本；例如，可使用如下基本定义：

```latex
\newtoks\headertoks% 一个用于页眉文本的新令牌列表变量
\newtoks\footertoks% 一个用于页脚文本的新令牌列表变量
% 使用 \global 以确保存储的页眉或页脚数据
% 可在任何地方访问，包括
% 输出例程中的命令
\newcommand{\myheader}[1]{\global\headertoks={#1}}
\newcommand{\myfooter}[1]{\global\footertoks={#1}}
```

其中 `\headertoks` 和 `\footertoks` 是用于存储页眉和页脚文本的令牌列表。

此外，我们的假设示例还具有一个输出例程，它从以下当前值中获取页眉和页脚文本： `\myheader` 和 `\myfooter`。如图所示，在将要用于当前页面的内容中， `\myheader` 和 `\myfooter` 设置适当（正确）的值。然而，也有可能 `\myheader` 和/或 `\myfooter` 在一个跨越分页点的段落中被调用。由于 TeX 必须处理整个段落， `\myheader` 和 `\myfooter` 会再次被调用，以重置保存在“TeX 内部状态”中的页眉/页脚文本，从而生成不适用于当前页面的值。当输出例程生成页眉和页脚时，它们将包含为下一页准备的文本。

![如果输出例程仅使用 TeX 宏来检索页眉和页脚文本，则会产生错误的页眉和页脚的示意图](/files/6326d08edf85452f96674162933e77dc314e2465)

#### TeX 的异步行为：\mark 命令来救场

TeX 的排版（内容构建）活动 *并不同步* 于通过输出例程中所包含的命令输出已排版页面的最终过程。到了输出页面的时候，TeX 的实际内部状态会领先于分页点处可能具有的任何状态——分页点正是它在当前正在输出的页面上排版最后一段内容的位置。

在 [《TeXbook》](https://www.amazon.co.uk/TeXbook-Donald-Knuth/dp/0201134489)，TeX 的创造者 Donald Knuth 将这种情况描述为

> ... TeX 的输出例程落后于其页面构建活动

由于这种不同步的关系，就需要以 *标记机制*的形式提供“特殊设施”，并通过 `\mark` （或者 `\marks n`）命令来应用，以确保页眉和页脚包含与实际正在输出的页面相关的内容。 `\mark` 命令会将标记节点嵌入页面内容中，存储可用于所在页面页眉和页脚的内容内存位置。

这本广为人知且备受尊敬的书的作者 [《TeX by Topic》](https://www.eijkhout.net/tex/tex-by-topic.html) 将 `\mark` 机制描述为

> ... 输出例程获取当前被截断页面内容信息的主要机制，尤其是页面顶部和底部。

该 `\mark` 命令是一种旨在规避 TeX 分页和输出例程算法异步特性的机制。

![嵌入页面正文内容中的标记数据（节点）示意图](/files/b9552746176e233171e4425702ed462bd1affa04)

### 输出前检查：查找标记节点

在 TeX 引擎确定分页位置后，它会调用一个内部例程（称为 `fire_up()`）在输出例程完成页面构成并将完成的页面写入 PDF 文件之前，它会执行大量预处理。这些预处理步骤中的一个重要部分，是设置三个 *全局标记变量*: `\botmark`, `\topmark` 和 `\firstmark` ，它们用于为构建页眉和页脚提供数据。当然，一个页面可以包含多个标记节点，但这些节点会被筛选以生成三个标记变量，其作用如下：

* **`\botmark`** 是在当前分页点生效的标记；即，在 *当前* page
* **`\topmark`** 是 `\botmark` 来自 *前一* page
* **`\firstmark`** 是 *当前* 页上的第一个标记——即介于 `\topmark` 和 `\botmark`

下图示出了对已排版页面内容进行预处理以确定三个全局标记变量值的基本原理 `\botmark`, `\topmark` 和 `\firstmark`:

![用于确定三个全局标记变量 \botmark、\topmark 和 \firstmark 的基本原理示意图](/files/6bed896d150d454e78c10ccb7bf52ee8bb614edd)

下一节将使用上面提供的一些解释来概述 LaTeX 如何排版页眉和页脚。

### LaTeX 的类：页面样式与页眉/页脚

当你写下 `\documentclass{*类*}` 其中 `*类*` 可能是 `article`, `book`, `报告` 或 `letter`时，LaTeX 会加载一个名为 `*类*.cls`；例如， `book.cls`, `article.cls` 等等。那些 `.cls` 文件包含 LaTeX 代码，用于实现各种命令和功能在不同类中的特定变体；这些命令和功能应当存在于并由所有文档类提供——也就是所有文档类共享的行为。示例可能包括类特定的页面布局边距，以及某些命令的类特定版本，例如 `\section`, `\subsection` 等等。

另一个重要元素是 `.cls` 文件中对标准页面样式的类特定实现，这些页面样式定义了使用特定类和页面样式生成的文档的页眉和页脚。例如：

* **`book.cls`** 和 **`article.cls`** 都包含以下页面样式的实现： `headings` 和 `myheadings`
* **`letter.cls`** 包含以下页面样式的定义： `headings`, `empty`, `plain` 以及一个名为 `firstpage`
* **`report.cls`** 包含以下页面样式的定义： `headings` 和 `myheadings`

页面样式的实现通常取决于 `twoside` 的选项 `\documentclass`——即文档是单面还是双面——这样页眉和页脚就能针对左页（偶数页）和右页（奇数页）进行适当定义。

文档类可能依赖于 `plain` 或 `empty` LaTeX 中其他地方提供的页面样式默认定义——这些定义位于 `.cls` 所使用的文件之外。

#### 关于页面样式的更多内容

当你使用 `\pagestyle{*某种样式*}` 或 `\thispagestyle{*某种样式*}` LaTeX 期望找到一个名为 `ps@*某种样式*`——通常由文档类提供 `.cls` 文件，或者某个诸如 `fancyhdr`。顺便说一句，所谓“内部命令”，是指 LaTeX 源代码的一部分、其名称中包含 `@` 符号的命令，这通常会阻止随意使用（由于其 [类别码](/latex/zh-cn/geng-duo-zhu-ti/43-table-of-tex-category-codes.md)).

的定义 `\ps@*某种样式*` 负责实现该页面样式的特性/行为，其中包括提供以下定义：

* 在页面构成最后阶段使用的命令（用于添加页眉和页脚，参见 [本文后面](#commands-for-the-output-routine)):
  * `\@oddhead`
  * `\@oddfoot`
  * `\@evenhead`
  * `\@evenfoot`
* 用于插入与节相关的标记数据的命令，以便生成包含节标题、编号及类似内容的文档页眉和页脚。例如， `headings` 页面样式（在 `book.cls`）中定义了以下会产生标记的命令：
  * `\chaptermark`
  * `\sectionmark`

我们将 [进一步探讨生成标记的命令](#mark-commands-and-mark-data)，例如 `\chaptermark`，不过这里只举个例子：当你写下 `\chapter{*章节标题*}` 该 `\chaptermark` 命令也会被执行，以插入与章节相关的标记数据，供后续生成页眉使用。

#### 基于页面样式的页眉和页脚命令剖析

本节探讨用于创建页眉和页脚的一些核心 LaTeX 命令/过程——借助本文前面介绍的解释或概念。

**LaTeX 的页眉和页脚“模型”**

LaTeX（默认）页眉和页脚的内容基于文档节的两级层次结构：一个“高层级”节包含多个“低层级”节。对于 book 和 article 类，这可表示为：

* `**book**` **类别**：“高层级”节由 `\chapter` 命令生成，而“低层级”节通过 `\section`生成，这反映了章节包含多个节。
* `**article**` **类别** （假设为双面文档）：“高层级”节使用 `\section` 命令生成，而“低层级”节通过 `\subsection`生成，这反映了同一节中出现多个小节。

LaTeX 通过其分节命令来应用其页眉和页脚“模型”，这些分节命令会（通过 `\mark` 命令）“注入”标记节点，以存储与正在创建的文档节相关的数据。例如，每当你写下 `\section{*某个节标题*}` 都会创建一个标记节点，其中包含反映 `*某个节标题*` 以及节编号的数据。

LaTeX 使用一种标记系统，其数据形式为 `{左}{右}`，最终通过 `\mark` 命令插入边注

```latex
\mark{{左}{右}}
```

并被多层宏所包裹，旨在将用户与更底层的细节隔离开来——这些细节将在本文后续部分探讨。

#### 探索 book 类

当 `book.cls` 文件被加载时，它的最后几个动作之一是执行 `\pagestyle{headings}` ，这会将默认页面样式设为 `headings`，因此我们将更详细地查看该页面样式。

如 [上所述](#more-on-page-styles)， `headings` 页面样式的特性/属性，由 `book` 类实现，将由（内部）命令 `\ps@headings` 在文件中定义 `book.cls`。 `\ps@headings` 的精确定义取决于文档是单面还是双面（这是 `book` 类的默认设置）。

**“标记命令”与“标记数据”**

对于由 `book` 类生成的双面文档， `\ps@headings` 的定义会创建（定义）两个“标记命令”： `\chaptermark` 和 `\sectionmark` 它们会产生“标记数据”，形式为 `{左}{右}`，供页眉和页脚使用。这些标记命令反映了 LaTeX 页眉和页脚的模型——基于文档节的层次结构：

* “高层级”节命令（这里是 `\chapter`）用于为左页（偶数页）页眉提供“标记数据”。当你通过写下 `\chapter{*章节标题*}`, `\chaptermark` 时，会调用 `*章节标题*` 和编号的“标记数据”。
* “较低层级”的节命令（此处， `\section`）用于为面向右侧（奇数页）的页面提供“标记数据”。当你写下 `\section{*section title*}` 该 `\sectionmark` 命令时，将调用该命令来创建“标记数据”，它反映新的节——包含其 `*section title*` 及编号。前一个 `\chapter` 提供的任何标记数据都不受随后任何 \section 命令的影响。

**\markboth 和 \markright**

LaTeX 的、针对节的标记命令，例如 `\chaptermark` 和 `\sectionmark`，还会使用另外两个命令，称为 `\markboth` 和 `\markright` ，它们为页眉和页脚提供实际的标记数据。 `\markboth` 和/或 `\markright` 可以包含用于设置页眉或页脚文本样式的命令，例如将其转换为大写。

对于使用 `book` 类默认设置（双面， `headings` 页面样式）生成的文档，生成标记的命令工作如下：

* `\chaptermark` 使用 `\markboth`
* `\sectionmark` 生成标记数据 `\markright`

`\markboth` 和 `\markright` 是宏，最终会使用原始的（内置的、低层级的） `\mark` 命令来实际插入由分节命令内部生成的标记。

`\markboth` 其形式为

* **`\markboth{left}{right}`**：使用 `\mark{{左}{右}}` 插入一个标记（节点），从而得到一个包含一对花括号值的标记节点 `{左}{右}`.

`\markright` 其形式为

* **`\markright{newright}`**：使用 `\mark{{currentleft}{newright}}`；即，它改变了 `right` 更改为 `的当前值` ，但 `left` （即， `的当前值`）保持不变，从而得到一个包含一对花括号值的标记节点 `{currentleft}{newright}`.

的行为 `\markboth` 和 `\markright` 支持两层级的节次结构，例如每一章中包含多个节的文档。

以下以（双面） `book` 文档类（使用 headings 页面样式）为例：

* `\chaptermark` 使用 `\markboth` 来设置：
  * a `{left}` 字段，该字段将 CHAPTER . 排版为大写。
  * 一个空白的 {right} 字段（`{}`)
* `\sectionmark` 使用 `\markright` 来设置 `{right}` 标记字段，使其排版为。，同样是大写。

要理解为什么 `\sectionmark` 使用 `\markright` 我们可以观察到，每一个新的 `\section` 命令都应当：

* 插入一个新的标记，其中包含该 `\section` 的数据（例如其标题和编号）。
* 但 *不* 影响当前 `\chapter` 中的标记数据值， `\section` 该特定的

命令出现于其中。 `\section` 基于这些原因， `\sectionmark` 命令会调用 `\markright` ，后者使用 `\section`来创建一个 `{right}` 相关的标记：它更新 `{left}` 标记数据字段，但不会影响当前由当前 `\chapter`.

设置的标记数据字段值。

> 注意：LaTeX 源代码包含以下提示：The marking commands work reasonably well for right marks ‘numbered within’ left marks—e.g., the left mark is changed by a \chapter command and the right mark is changed by a \section command. However, it does produce somewhat anomalous results if 2 \markboth’s occur on the same page.

**输出例程的命令**

该 `\ps@headings` 命令实现了以下命令的定义

* `\@oddhead`
* `\@oddfoot`
* `\@evenhead`
* `\@evenfoot`

，这些命令由输出例程在页面排版的最后阶段用于添加页眉和页脚。例如， `book.cls` 将 `\@oddfoot` 和 `\@evenfoot` 设置为值 `\@empty`插入我们的示例命令，它被定义为 `\def\@empty{}`，从而 `\ps@headings` 在左页和右页上产生空白页脚：

```latex
\let\@oddfoot\@empty
\let\@evenfoot\@empty
```

页眉定义如下：

```latex
\def\@evenhead{\thepage\hfil\slshape\leftmark}%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
```

其中

* `\leftmark` 从 `{left}` 中提取 `{左}{右}` 值的标记对
* `\rightmark` 从 `{right}` 中提取 `{左}{右}` 值的标记对
* \thepage 输出当前页码

本质上，这些 `\@evenhead` 和 `\@oddhead` 会产生如下结果：

* `\@evenhead`：页码出现在页眉左侧，而 `\leftmark`的内容，使用字体命令 `\slshape`进行样式设置后，输出在页眉右侧——中间的空白由极其灵活的 `\\hfil` 间距胶的使用。
* `\@oddhead`： `\rightmark`的内容，使用字体命令 `\slshape`的内容出现在页眉左侧，而当前页码输出在页眉右侧——同样，中间的空白由 `\\hfil` 间距胶的使用。

提供。然而，仍然有一个关键问题： *哪些* `{左}{右}` 标记对被 `\leftmark` 和 `\rightmark`使用：换句话说，它们从哪里获取其标记数据值？

如 [上所述](#filtering) 但 [如下所述](#explainfiltering)，在最终的页面排版阶段，文档页面中包含的任何标记都会被“过滤”，并用于设置三个全局标记变量的值： `\botmark`, `\topmark` 和 `\firstmark`。由于 LaTeX 标记的 [结构，](#latexs-model-of-headers-and-footers)这 3 个标记变量中的每一个最终都会包含一个 `{左}{右}` 值，针对某一对值 `{left}` 和 `{right}` 而正是它们提供了实际的 `{左}{右}` 标记对（字段），用于 `\leftmark` 和 `\rightmark`:

* `\leftmark` 从 `left` 来自 `{左}{右}` 提供的标记对（字段）中的值 `\botmark`
* `\rightmark` 从 `right` 来自 `{左}{右}` 提供的标记对 `\firstmark`

注意 `\markboth` 和 `\markright` 可以包含用于设置页眉和页脚文本样式的命令——例如转换为大写。

### 使用较低层级 LaTeX 命令的示例

下面的示例展示了如何通过重新定义本文中讨论的一些低层级（内部）LaTeX 命令（那些包含 @ 符号的命令）来更改页面样式或页眉和页脚。我们并不是推荐用这种方法来修改页眉和页脚，但对于需要它的人（例如包作者）来说，这是可行的。更推荐的修改页眉和页脚的方法是使用 [`fancyhdr` 宏包](https://ctan.org/pkg/fancyhdr?lang=en)，其在一篇 [Overleaf 帮助文章](/latex/zh-cn/ge-shi-hua/02-headers-and-footers.md).

#### 定义一个最小页面样式

下面的示例定义了一个新的、极其简化的页面样式，名为 `demostyle` ，它使用静态文本来定义页眉和页脚，并且不依赖分节命令（`\chapter`, `\section` 等等）来设置页眉和页脚内容。

该示例首先将 `@` 字符的类别码改为 11，以便它可以在宏名中使用。 `\ps@demostyle` 命令通过重新定义（在输出例程中）用于生成页眉和页脚的命令来实现我们的最小页面样式： `\@oddhead`, `\@oddfoot`, `\@evenhead` 和 `\@evenfoot`。注意我们 `twoside` 声明中的 `\documentclass` 选项——它使用 `article` 类。

```latex
\documentclass[twoside]{article}
\catcode`@=11
\newcommand{\ps@demostyle}{%
\renewcommand\@oddfoot{\hfil The odd-page footer\hfil}%
\renewcommand\@evenfoot{\hfil The even-page footer\hfil}%
\renewcommand\@evenhead{\thepage\hfil The even-page header}%
\renewcommand\@oddhead{The odd-page header\hfil\thepage}}
\catcode`@=12
\title{Demonstrating a page style}
\author{Overleaf}
\date{August 2022}
\begin{document}
\pagestyle{demostyle}
\maketitle
\newpage
\section{引言}
\newpage
\section{More material}
\end{document}
```

[在 Overleaf 中打开此示例](https://www.overleaf.com/docs?engine=pdflatex\&snip_name=Creating+a+new+page+style\&snip=%5Cdocumentclass%5Btwoside%5D%7Barticle%7D%0A%5Ccatcode%60%40%3D11%0A%5Cnewcommand%7B%5Cps%40demostyle%7D%7B%25%0A%5Crenewcommand%5C%40oddfoot%7B%5Chfil+The+odd-page+footer%5Chfil%7D%25%0A%5Crenewcommand%5C%40evenfoot%7B%5Chfil+The+even-page+footer%5Chfil%7D%25%0A%5Crenewcommand%5C%40evenhead%7B%5Cthepage%5Chfil+The+even-page+header%7D%25%0A%5Crenewcommand%5C%40oddhead%7BThe+odd-page+header%5Chfil%5Cthepage%7D%7D%0A%5Ccatcode%60%40%3D12%0A%5Ctitle%7BDemonstrating+a+page+style%7D%0A%5Cauthor%7BOverleaf%7D%0A%5Cdate%7BAugust+2022%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cpagestyle%7Bdemostyle%7D%0A%5Cmaketitle%0A%5Cnewpage%0A%5Csection%7BIntroduction%7D%0A%5Cnewpage%0A%5Csection%7BMore+material%7D%0A%5Cend%7Bdocument%7D)

#### 更改 book 类的页眉

下面的示例通过重新定义 `\@oddhead` 和 `\@evenhead` 来更改 book 类的页眉，使左页和右页页眉包含当前页码和章节标题。请注意第二个 `\chapter` 命令如何使用可选的简短章节标题 `[简短标题]` ，它现在提供页眉文本，而不是长标题。

`\section` 命令不会影响页眉内容，因为我们的页眉重新定义只使用 `\leftmark` ，后者获取当前章节的详细信息。若要访问当前节的详细信息，我们需要在 `\rightmark` 的定义中使用 `\@oddhead` 和/或 `\evenhead`.

```latex
\documentclass{book}
% 注意：'@' 的类别码暂时改为 11
% 以允许它在命令名中使用
\catcode `@=11
% 令 \@oddfoot 和 \evenfoot 等同于 \@empty
% 使它们为空（空白）
\let\@oddfoot\empty\let\evenfoot\empty
% 重新定义 \@oddhead 和 \@evenhead
\renewcommand{\@oddhead}{\leftmark\hfil\thepage}
\renewcommand{\@evenhead}{\thepage\hfil\leftmark}
\catcode `@=12
% 使用一个方便的小页面尺寸
\usepackage[paperheight=16cm,paperwidth=12cm,textwidth=10cm]{geometry}
\title{\TeX{} 用户回忆录}
\author{Overleaf}
\begin{document}
\frontmatter
\maketitle
这是前言部分，使用罗马数字。
\mainmatter
\chapter{我从哪里开始？}
第 1 章：一个简短的章节。
\newpage
\section{In the beginning...}
一个小节。
\chapter[简短标题]{一个标题非常长、因而不适合用于页眉的章节}
\newpage
\section{另一个小节}
内容很少。
\newpage
\section{什么，另一个小节？}
内容也很少。
\end{document}
```

[在 Overleaf 中打开此示例](https://www.overleaf.com/docs?engine=pdflatex\&snip_name=Changing+headers+for+the+book+class\&snip=%5Cdocumentclass%7Bbook%7D%0A%25+NB%3A+category+code+of+%27%40%27+temporarily+changed+to+11%0A%25+to+enable+its+use+in+command+names%0A%5Ccatcode+%60%40%3D11%0A%25+Let+%5C%40oddfoot+and+%5Cevenfoot+be+equivalent+to+%5C%40empty%0A%25+to+make+them+blank+%28empty%29%0A%5Clet%5C%40oddfoot%5Cempty%5Clet%5Cevenfoot%5Cempty%0A%25+Redefine+%5C%40oddhead+and+%5C%40evenhead%0A%5Crenewcommand%7B%5C%40oddhead%7D%7B%5Cleftmark%5Chfil%5Cthepage%7D%0A%5Crenewcommand%7B%5C%40evenhead%7D%7B%5Cthepage%5Chfil%5Cleftmark%7D%0A%5Ccatcode+%60%40%3D12%0A%25+Use+a+conveniently+small+page+size%0A%5Cusepackage%5Bpaperheight%3D16cm%2Cpaperwidth%3D12cm%2Ctextwidth%3D10cm%5D%7Bgeometry%7D%0A%5Ctitle%7BMemoirs+of+a+%5CTeX%7B%7D+user%7D%0A%5Cauthor%7BOverleaf%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cfrontmatter%0A%5Cmaketitle%0AThis+is+frontmatter+which+uses+Roman+numerals.%0A%5Cmainmatter%0A%5Cchapter%7BWhere+do+I+start%3F%7D%0AChapter+1%3A+A+short+chapter.%0A%5Cnewpage%0A%5Csection%7BIn+the+beginning...%7D%0AA+section.%0A%5Cchapter%5BThe+short+title%5D%7BA+chapter+with+a+very+long+title%2C+making+it+unsuitable+for+headers%7D%0A%5Cnewpage%0A%5Csection%7BAnother+section%7D%0AWith+little+content.%0A%5Cnewpage%0A%5Csection%7BWhat%2C+another+section%3F%7D%0AAlso+with+little+content.%0A%5Cend%7Bdocument%7D)

#### 使用 ε-TeX 扩展的标记命令

该 `book` 类使用以下对 `\@evenhead` 和 `\@oddhead` 的定义来生成页眉：

```latex
\def\@evenhead{\thepage\hfil\slshape\leftmark}
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}
```

其中命令 `\leftmark` 和 `\rightmark` 被定义为

```latex
\def\leftmark{\expandafter\@leftmark\botmark\@empty\@empty}
\def\rightmark{\expandafter\@rightmark\firstmark\@empty\@empty}
```

利用 ε-TeX 的扩展命令，以及 ε-TeX 的标记类 0 与原始 TeX 命令之间的等价关系，我们可以：

* 将 `\leftmark` 来替换 `\botmark` 改写为使用 ε-TeX 的 `\botmarks0` 等价形式，并将
* 将 `\rightmark` 来替换 `\firstmark` 改写为使用 ε-TeX 的 `\firstmarks0` 视为等价

这些重新定义产生

```latex
\renewcommand{\leftmark}{\expandafter\@leftmark\botmarks0\relax\@empty\@empty}
\renewcommand{\rightmark}{\expandafter\@rightmark\firstmarks0\relax\@empty\@empty}
```

注意使用 `\relax` 来终止 TeX 对更多数字的搜索——你也可以用一个空格替换 `\relax` ，将其作为终止符。

下面的示例产生的输出与 `\leftmark` 和 `\rightmark`.

```latex
\documentclass{book}
的原始定义完全相同
% 重新定义 \leftmark 和 \rightmark，分别使用
% 注意：'@' 的类别码暂时改为 11
% 以允许它在命令名中使用
\catcode `@=11
\renewcommand{\leftmark}{\expandafter\@leftmark\botmarks0\relax\@empty\@empty}
\renewcommand{\rightmark}{\expandafter\@rightmark\firstmarks0\relax\@empty\@empty}
\catcode `@=12
% 使用一个方便的小页面尺寸
\usepackage[paperheight=16cm,paperwidth=12cm,textwidth=10cm]{geometry}
\title{\TeX{} 用户回忆录}
\author{Overleaf}
\begin{document}
\frontmatter
\maketitle
这是前言部分，使用罗马数字。
\mainmatter
\chapter{我从哪里开始？}
第 1 章：一个简短的章节。
\newpage
\section{In the beginning...}
一个小节。
\newpage
另一个小节。
\end{document}
```

[在 Overleaf 中打开此示例](https://www.overleaf.com/docs?engine=pdflatex\&snip_name=Using+%CE%B5-TeX%E2%80%99s+extended+marks+command\&snip=%5Cdocumentclass%7Bbook%7D%0A%25+Redefine+%5Cleftmark+and+%5Crightmark+to+use+%0A%25+%5Cbotmarks0+and+%5Cfirstmarks0+respectively%0A%25+NB%3A+category+code+of+%27%40%27+temporarily+changed+to+11%0A%25+to+enable+its+use+in+command+names%0A%5Ccatcode+%60%40%3D11%0A%5Crenewcommand%7B%5Cleftmark%7D%7B%5Cexpandafter%5C%40leftmark%5Cbotmarks0%5Crelax%5C%40empty%5C%40empty%7D%0A%5Crenewcommand%7B%5Crightmark%7D%7B%5Cexpandafter%5C%40rightmark%5Cfirstmarks0%5Crelax%5C%40empty%5C%40empty%7D%0A%5Ccatcode+%60%40%3D12%0A%25+Use+a+conveniently+small+page+size%0A%5Cusepackage%5Bpaperheight%3D16cm%2Cpaperwidth%3D12cm%2Ctextwidth%3D10cm%5D%7Bgeometry%7D%0A%5Ctitle%7BMemoirs+of+a+%5CTeX%7B%7D+user%7D%0A%5Cauthor%7BOverleaf%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cfrontmatter%0A%5Cmaketitle%0AThis+is+frontmatter+which+uses+Roman+numerals.%0A%5Cmainmatter%0A%5Cchapter%7BWhere+do+I+start%3F%7D%0AChapter+1%3A+A+short+chapter.%0A%5Cnewpage%0A%5Csection%7BIn+the+beginning...%7D%0AA+section.%0A%5Cnewpage%0AAnother+section.%0A%5Cend%7Bdocument%7D)

**关于 ε-TeX 的说明**

如 [我们在上面已经指出](#etex)，ε-TeX 引入了 `\marks` 命令设置为特定值：

```latex
\marks n {要存储的内容}
```

，它扩展了 Knuth 的 TeX 引擎原有的 `\mark` 功能，并且可用于三种主流 TeX 引擎：pdfTeX、LuaTeX 和 XeTeX。

ε-TeX 的实现还引入了新的全局标记变量 `\firstmarks n`, `\botmarks n` 和 `\topmarks n`：每个类各一个 `n`。这些基于类的标记变量会被确定 [使用上面概述的机制](#pre-output-checks-looking-for-mark-nodes)，并且都可以用于生成页眉和页脚。

你可以在 $$2^{15}$$ = 32768 个标记类中的任意一个（从 0 到 32767）中使用 `\leftmark` 或 `\rightmark`的重新定义。前提是你已经为所选的标记类提供（插入）了合适的标记数据， `n`，通过 `\marks n{{left}{right}}`.

请注意，LuaTeX 提供 $$2^{16}$$ = 65536 个标记类， `n` 的范围为 0 到 65535。

**使用标记类 10 的示例**

这个示例演示了使用标记类 10（随机选取）来创建文档页眉。示例文档排版了 6 页，其中包含在本文末尾的 [已完成示例](#a-worked-example-to-show-how-tex-engines-determine-values-for-botmark-topmark-and-firstmark)中给出的标记序列。研究该示例将显示为什么 TeX 引擎为每个页眉选择了某个特定的标记， $$\alpha$$, $$\beta$$, $$\gamma$$ 和 $$\delta$$。

我们先定义一个便捷宏， `\domark`，它使用 `\marks 10` 来插入符合 LaTeX `{左}{右}` 约定的标记数据：

```latex
\newcommand{\domark}[2]{\marks 10{{#1}{#2}}}
```

通过重新定义 `\leftmark` 和 `\rightmark`，可让 10 类标记对 LaTeX 的页眉和页脚机制可访问——并额外加入了一点信息：

```latex
\renewcommand{\leftmark}{\expandafter\@leftmark\botmarks10 \@empty\@empty{} (via \texttt{\string\botmarks10})}
\renewcommand{\rightmark}{\expandafter\@rightmark\firstmarks10 \@empty\@empty{} (via \texttt{\string\firstmarks10})}
```

在插入若干（10 类）标记后，TeX 引擎（此处为 pdfTeX）会顺利地为 10 类创建全局标记变量——`\botmarks10`, `\topmarks10` 和 `\firstsmarks10` 哪些 `\leftmark` 和 `\rightmark` 用于生成页眉。

```latex
\documentclass{book}
% 一个用于使用 10 类标记的简短命令
% 并遵循 LaTeX 的标记结构 {left}{right}
\newcommand{\domark}[2]{\marks 10{{#1}{#2}}}
的原始定义完全相同
% 分别对应 \botmarks10 和 \firstmarks10
\catcode`@=11
\renewcommand{\leftmark}{\expandafter\@leftmark\botmarks10 \@empty\@empty{} (via \texttt{\string\botmarks10})}
\renewcommand{\rightmark}{\expandafter\@rightmark\firstmarks10 \@empty\@empty{} (via \texttt{\string\firstmarks10})}
\catcode`@=12
\title{演示 \(\varepsilon\)-\TeX 的增强标记}
\author{Overleaf}
\date{August 2022}
\begin{document}

第 1 页：没有添加标记，因此所有标记变量都保持在初始化状态：空（NULL）。

\newpage
第 2 页：通过

\verb|\domark{$\alpha$-left}{$\alpha$-right}|\domark{$\alpha$-left}{$\alpha$-right}

\newpage
向该页添加了 $\alpha$ 标记。

\newpage
第 4 页：通过

\verb|\domark{$\beta$-left}{$\beta$-right}|

\verb|\domark{$\gamma$-left}{$\gamma$-right}|。
\domark{$\beta$-left}{$\beta$-right}
\domark{$\gamma$-left}{$\gamma$-right}

\newpage
第 5 页：通过

\verb|\domark{$\delta$-left}{$\delta$-right}|
\domark{$\delta$-left}{$\delta$-right}

\newpage
第 6 页：没有向该页添加标记。
\end{document}
```

[在 Overleaf 中打开此示例](https://www.overleaf.com/docs?engine=pdflatex\&snip_name=Using+e-TeX+global+marks+variables\&snip=%5Cdocumentclass%7Bbook%7D%0A%25+A+short+command+to+use+marks+with+class+10%0A%25+and+following+LaTeX%27s+mark+structure+%7Bleft%7D%7Bright%7D%0A%5Cnewcommand%7B%5Cdomark%7D%5B2%5D%7B%5Cmarks+10%7B%7B%231%7D%7B%232%7D%7D%7D%0A%25+Redefine+%5Cleftmark+and+%5Crightmark+to+use+%0A%25+%5Cbotmarks10+and+%5Cfirstmarks10+respectively%0A%5Ccatcode%60%40%3D11%0A%5Crenewcommand%7B%5Cleftmark%7D%7B%5Cexpandafter%5C%40leftmark%5Cbotmarks10+%5C%40empty%5C%40empty%7B%7D+%28via+%5Ctexttt%7B%5Cstring%5Cbotmarks10%7D%29%7D%0A%5Crenewcommand%7B%5Crightmark%7D%7B%5Cexpandafter%5C%40rightmark%5Cfirstmarks10+%5C%40empty%5C%40empty%7B%7D+%28via+%5Ctexttt%7B%5Cstring%5Cfirstmarks10%7D%29%7D%0A%5Ccatcode%60%40%3D12%0A%5Ctitle%7BDemonstrating+%5C%28%5Cvarepsilon%5C%29-%5CTeX%E2%80%99s+enhanced+marks%7D%0A%5Cauthor%7BOverleaf%7D%0A%5Cdate%7BAugust+2022%7D%0A%5Cbegin%7Bdocument%7D%0A%0APage+1%3A+No+marks+added+so+all+mark+variables+remain+in+their+initialized+state%3A+empty+%28NULL%29.%0A%0A%5Cnewpage%0APage+2%3A+%24%5Calpha%24-mark+added+to+this+page+via%0A%0A%5Cverb%7C%5Cdomark%7B%24%5Calpha%24-left%7D%7B%24%5Calpha%24-right%7D%7C%5Cdomark%7B%24%5Calpha%24-left%7D%7B%24%5Calpha%24-right%7D%0A%0A%5Cnewpage%0APage+3%3A+No+new+marks+added+to+this+page.%0A%0A%5Cnewpage%0APage+4%3A+%24%5Cbeta%24-mark+followed+by+%24%5Cgamma%24-mark+added+to+this+page+via%0A%0A%5Cverb%7C%5Cdomark%7B%24%5Cbeta%24-left%7D%7B%24%5Cbeta%24-right%7D%7C%0A%0A%5Cverb%7C%5Cdomark%7B%24%5Cgamma%24-left%7D%7B%24%5Cgamma%24-right%7D%7C.%0A%5Cdomark%7B%24%5Cbeta%24-left%7D%7B%24%5Cbeta%24-right%7D%0A%5Cdomark%7B%24%5Cgamma%24-left%7D%7B%24%5Cgamma%24-right%7D%0A%0A%5Cnewpage%0APage+5%3A+%24%5Cdelta%24-mark+added+to+this+page+via%0A%0A%5Cverb%7C%5Cdomark%7B%24%5Cdelta%24-left%7D%7B%24%5Cdelta%24-right%7D%7C%0A%5Cdomark%7B%24%5Cdelta%24-left%7D%7B%24%5Cdelta%24-right%7D%0A%0A%5Cnewpage%0APage+6%3A+No+marks+added+to+this+page.%0A%5Cend%7Bdocument%7D)

下图显示了此示例生成的页眉：

![显示使用使用 ε-TeX 扩展标记命令的 LaTeX 代码输出的图像](/files/a3c7dc9064365545a34be2bf0e0b01ed8518c5d0)

* **注意**：第 1 页上的页眉是在 10 类的所有三个标记变量——`\botmarks10`, `\topmarks10` 和 `\firstsmarks10`——都为空时创建的。最终结果是 `\rightmark` （针对第 1 页）只插入最后一个页眉片段： `（通过 \texttt{\string\firstmarks10})`，如上图所示。

## 一个已完成示例，说明 TeX 引擎如何确定 \botmark、\topmark 和 \firstmark 的值

![Db.gif](/files/37cc1f2b45a72bae51ccc2d79a160c50fd2caf79)![Db.gif](/files/37cc1f2b45a72bae51ccc2d79a160c50fd2caf79) 仿效 Knuth 使用双重危险弯折符号似乎很合适（图片鸣谢 [本网站](http://www.truetex.com/db.htm)），因为这些内容多少有些低层级，并且是在“窥视幕后”——尽管我们希望它能让勇于探索的读者感兴趣，并愿意了解更多细节。

下面的讨论主要基于 [《TeXbook》](https://www.amazon.co.uk/TeXbook-Donald-Knuth/dp/0201134489)第 258 页上的最后一个示例。此处，我们已将该示例编辑并重新排版，供无法访问 The TeXbook 的读者参考：

![来自 The TeXbook 第 258 页示例的编辑并重新排版版本](/files/3b6baf624fddb506fd5aabd67811d218e97327e3)

此图中展示的表格重现自 Knuth 提供的一个表格，但与原始版本一样，并未解释它是如何推导出来的。这里我们将说明这一点，以提供更多细节，展示这些 `\botmark`, `\topmark` 和 `\firstmark` 的值是如何确定的——获得这些细节需要探索一个 TeX 引擎的源代码！

设置 `\botmark`, `\topmark` 和 `\firstmark` 的值的过程，可以用如下图及后续说明中的 3 段简短伪代码来概括：

1. 前内容检查
2. 内容循环
3. 后内容检查

注意：

* 步骤 1“前内容检查”和步骤 3“后内容检查”每页执行一次。
* 步骤 2“内容循环”涉及 TeX 扫描页面内容，查找标记节点，并按图所示用它们来设置标记变量值。

![显示用于设置 TeX 引擎所使用的三个全局标记变量值的代码伪代码版本的图像](/files/f6cee0f173f1f6942bbeb4869c71ce43f18e65f1)

每当 TeX 找到合适的分页点时，它就会把该页的内容发送进行最终处理，并对文档中的每一页执行这些（3 个）标记处理步骤。标注为 1、2 和 3 的处理步骤在本节最后的图中被引用。

请注意：

* 在第一页被处理之前，三个全局标记变量 `\botmark`, `\topmark` 和 `\firstmark` 都已初始化为空（NULL）。
* 的最终值 `\botmark`, `\topmark` 和 `\firstmark` 在 *前一* 页结束时确定，并作为处理 *当前* 页——即当前正在处理的页面——时的（输入）值。

下面的代码片段是 ***伪代码*** 从实际 TeX 引擎源代码中提炼（总结）而来。目标是提供简洁的代码概述，以帮助展示所涉及的关键原理。

**步骤 1：前内容检查** 在 TeX 浏览当前页的实际内容之前，它会检查当前 `\botmark` 值——即由 *前一* 页产生的值——是否为空（NULL）：

```latex
if(\botmark != NULL)
{
   \topmark = \botmark;
   \firstmark= NULL;
}
```

在此测试中，如果 `\botmark` （来自 *前一* 页）的 *不* 为空：

* `\topmark` 则 *当前* 页的 *前一* 页 `\botmark` 值
* `\firstmark`，对于 *当前* 页（正在被处理的页面），则设为空（NULL）：在处理此页时，假定没有 `\mark` 节点。

注意，该测试是执行 *一次* 针对正在处理的每一页。

**步骤 2：内容循环** 接下来，TeX 会遍历页面内容；该循环的一部分包括查找 `\mark` 节点：

```latex
if(type(node)==mark_node)
{
   if(\firstmark==NULL)
   {
      \firstmark=node(text);
   }
   \botmark= node(text);
}
```

此测试适用于位于（包含在）当前页面中的每个标记节点。前面的测试（上面的步骤 1）可能已将 `\firstmark` 设为空，因此当前页面中找到的第一个标记将成为 `\firstmark` 和 `\botmark`。任何 *后续的* `\mark` 在执行该循环时检测到的节点都不会改变 `\firstmark`，因为它已不再为空（NULL），但是 `\botmark` 会被更新——因为这正是 `\botmark`的用途：用于存储最后看到的 `\mark` 本页的节点。

**步骤 3：内容后检查** 在遍历完页面内容后，TeX 会应用以下最终测试，该测试也会在每页执行一次：

```latex
if((\topmark != NULL)&&(\firstmark==NULL))
{
	\firstmark= \topmark;
}
```

* 如果 `\topmark` 不为空（NULL），那么我们从步骤 1 知道 `\botmark` 上一页的值不为空（NULL）。
* 如果 `\firstmark` 仍然为空，那么我们就没有看到任何 `\mark` 节点，因为步骤 2 中的测试未被触发；因此， `\firstmark` 被设为 `\topmark` 的值，这也就是 `\botmark` 来自上一页。

在对示例中的全部 6 页按顺序应用这三个步骤之后， `\botmark`, `\topmark` 和 `\firstmark` 的值通过下图所示的转换得出，其中：

* 浅蓝色圆角矩形表示每页的最终标记变量值
* 浅灰色圆角方块表示处理过程中产生的中间值
* 带标签的箭头表示根据上图中的步骤 1、2 或 3 所引起的标记变量值变化——或者该值未被任何步骤改变；标注为“无变化”。

下图显示这三个伪代码片段如何生成 `\botmark`, `\topmark` 和 `\firstmark` ，该值列于《TeXbook》第 258 页中的表格。

![用于配合《TeXbook》第 258 页示例的转换图，显示全局标记变量在被赋值时的内部转换](/files/48831704f7f74bee55fc6ba9add3cee60980d2de)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://overleaf-pro.ayaka.space/latex/zh-cn/shen-ru-wen-zhang/23-how-does-latex-typeset-headers-and-footers.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
