> For the complete documentation index, see [llms.txt](https://ayakaleaf-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://ayakaleaf-pro.ayaka.space/latex/zh-cn/shen-du-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-du-wen-zhang/55-what-s-in-a-name-a-guide-to-the-many-flavours-of-tex.md) 以获取更多信息。

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

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

### 内容存储说明：TeX 节点

当底层 TeX 引擎处理你的 LaTeX 代码以生成排版材料——例如段落、表格和数学公式——时，它需要将这些内容暂时存储在用于处理你的 LaTeX 代码的 TeX 引擎的内存中。为了存储排版内容，TeX 引擎使用一系列所谓的 *节点*，你可以把它们看作不同大小的计算机（设备）内存“块”，并通过链接形成一个列表（参见这篇 [维基百科关于链式数据结构的文章](https://en.wikipedia.org/wiki/Linked_data_structure)).

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

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

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

在众多节点类型中，我们需要讨论的一种叫作 *标记节点* ，它由 `\mark` 命令：

```latex
\mark{stuff to store}
```

该 `\mark` 命令创建，不会直接产生任何排版内容：其参数 `*stuff to store*` 会被保存到内存中供以后使用，并创建一个 *标记节点* 来包含 *stuff to store*的内存位置。正如我们将看到的， `\mark` 命令被创建 *专门* 用于辅助生成页眉和页脚。

由 `\mark` 命令创建的标记节点会“嵌入”到页面内容中，以便在页面构建的最后阶段、TeX 引擎准备向排版页面（正文）添加页眉和页脚时使用。通常， `*stuff to store*` 会包含诸如页码以及章或节的标题/编号之类的材料——这些都是页眉和页脚的组成部分。

#### ε-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 的一项增强移除了这一限制：\marks 命令，pdfTeX、LuaTeX 和 XeTeX 都支持它：

```latex
\marks n {stuff to store}
```

其中 `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* 的段落内容进入了当前页面：其“超出部分”会被保存起来，等待以后生成 *下一* 页。下面的图示说明了这个想法：段落的一部分在 *当前* 页上，另一部分则被保留到 *下一* 页面的“集成”部分中开启和关闭 Writefull for Overleaf。

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

这个段落示例表明，TeX 引擎经常会处理（排版）一些内容片段，这些内容 *完整地*，无法放入当前页面——因为这些内容延伸到了页面变得必须分页的那个位置之后。请记住，TeX 引擎通过先输入再排版来寻找换行， *整个段落* 与某些应用不同，它们并不是逐行排版段落。换句话说，TeX 会把 *整个* 段落排版出来，无论它是否能放入当前页面（这是稍后才确定的）。顺带一提，“先完整排版再添加到当前页面”这一原则也适用于其他类型的 TeX 内容，包括表格：表格会从 .tex 文件中完整读入并整体排版，然后才尝试将它们添加到当前页面。

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

尽管完整段落已经被排版，但其中有一部分无法被当前页面容纳，因此“超出段落内容”会被保存到 *下一* 页中。在产生“超出段落内容”的 LaTeX 代码（文本加命令）中，TeX 引擎 *可能* 调用了任意数量的命令和宏，这些命令和宏改变了 TeX 引擎内部存储的值和变量——也就是说，改变了 TeX 的“当前状态”。

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

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

请记住，一旦 TeX 有了一个“足够满”的页面（正文），它就会把该内容传递给一个称为 *输出例程* 输出例程

的东西，它是一系列用户定义的命令。其中，输出例程会为页面正文添加页眉和页脚，使页面最终完成并准备输出到 PDF 文件。 `\baselineskip` 上面的图示表示 TeX 在某个特定节点处决定分页，例如 *然而*，除了当前页面的内容（存储在内存中）之外，TeX 还已经处理并存储了（在内存中） *额外材料*，来自你的 `.tex` 文件。那部分额外材料延伸到了 *分页点之外* 和 *可能* ，并可能包含一些会改变重要页面相关值或变量的命令，从而影响 TeX 的“当前内部状态”——其中包括内部参数的值或宏存储的数据。

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

#### 假设性示例：如何得到错误的页眉

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

```latex
\newtoks\headertoks% a new token list variable for the header text
\newtoks\footertoks% a new token list variable for the footer text
% Use \global to ensure stored header or footer data is
% accessible everywhere, including commands in the
% output routine
\newcommand{\myheader}[1]{\global\headertoks={#1}}
\newcommand{\myfooter}[1]{\global\footertoks={#1}}
```

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

此外，我们的假设性示例还包含一个输出例程，它从当前的 `\myheader` 和 `\myfooter`值中获取页眉和页脚文本。 `\myheader` 和 `\myfooter` 如图所示，在当前页面的内容中， `\myheader` 和/或 `\myfooter` 设置适当（正确）的值。然而，在一个跨越分页点的段落中调用 `\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{*class*}` 其中 `*class*` 时， `article`, `book`, `report` 或 `letter`LaTeX 会加载一个名为 `*class*.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`——即文档是单面还是双面——这样页眉和页脚就能为左页（偶数页）和右页（奇数页）正确定义。

文档类可能依赖于 LaTeX 中其他地方提供的 `plain` 或 `empty` 页面样式的默认定义——也就是在 `.cls` 文件之外。

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

当你使用 `\pagestyle{*somestyle*}` 或 `\thispagestyle{*somestyle*}` 更改页面样式时，LaTeX 期望找到一个名为 `ps@*somestyle*`的内部命令——通常由文档类 `.cls` 文件或某个类似 `fancyhdr`。顺便说一下，这里所说的“内部命令”是指 LaTeX 源代码的一部分且其名称包含一个 `@` @ 符号，这通常会阻止随意使用（因为它具有 [类别码](/latex/zh-cn/geng-duo-zhu-ti/43-table-of-tex-category-codes.md)).

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

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

我们将 [进一步探讨生成标记的命令](#mark-commands-and-mark-data)，例如 `\chaptermark`，但仅举一例：当你写 `\chapter{chapter title}` 该 `\chaptermark` 时，命令也会被执行，以插入与章相关的标记数据，用于随后生成页面页眉。

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

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

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

LaTeX（默认）页眉和页脚的内容基于文档节的两层层级结构：一个“更高层级”的节包含多个“较低层级”的节。对于 book 和 article 类，这意味着：

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

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

LaTeX 使用一种标记系统，其数据形式为 `{left}{right}`，最终通过 `\mark` 命令

```latex
\mark{{left}{right}}
```

生成，并包裹在多层宏之中，以免用户接触到更底层的细节——这些细节将在本文后续章节中探讨。

#### 探究 book 类

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

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

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

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

* “更高层级”的节命令（这里是 `\chapter`）用于为左页（偶数页）页眉提供“标记数据”。当你写入一个新章节时， `\chapter{*chapter title*}`, `\chaptermark` 时，会调用 `*chapter title*` 和编号。
* “较低层级”的节命令（这里是 `\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{{left}{right}}` 插入一个标记（节点），生成一个包含一对花括号值的标记节点 `{left}{right}`.

`\markright` 形式如下

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

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

举例来说，对于（双面） `book` 文档类（使用标题页样式）：

* `\chaptermark` 使用 `\markboth` 来设置：
  * a `{left}` 字段，其会以大写形式排版 CHAPTER。
  * 一个为空白的 {right} 字段（`{}`)
* `\sectionmark` 使用 `\markright` 以设置 `{right}` 标记字段也以大写形式排版。

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

* 插入一个包含该 `\section` 的数据的新标记（例如其标题和编号）。
* 但是 *不* 影响当前 `\chapter` 中的标记数据值 `\section` 命令出现于其中的那个

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

注意：LaTeX 源代码包含如下提示：

> 这些标记命令对“在左标记内编号”的右标记来说相当有效——例如，左标记由 \chapter 命令更改，而右标记由 \section 命令更改。然而，如果同一页上出现 2 次 \markboth，则会产生某种异常结果。

**输出例程的命令**

该 `\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}` 中提取 `{left}{right}` 值
* `\rightmark` 从一个 `{right}` 中提取 `{left}{right}` 值
* \thepage 输出当前页码

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

* `\@evenhead`：页码显示在页眉左侧，而 `\leftmark`的内容（以字体命令 `\slshape`进行样式化）显示在页眉右侧——中间的空白由非常灵活的 `\hfil` 胶的使用。
* `\@oddhead`： `\rightmark`的内容（以字体命令 `\slshape`的内容显示在页眉左侧，而当前页码显示在页眉右侧——同样，中间的空白由 `\hfil` 胶的使用。

然而，一个关键问题仍然存在： *究竟是哪个* `{left}{right}` 标记对被 `\leftmark` 和 `\rightmark`使用：换句话说，它们从哪里获得自己的标记数据值？

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

* `\leftmark` 从一个 `左侧` 从 `{left}{right}` 提供的标记对（字段）中取值 `\botmark`
* `\rightmark` 从一个 `右侧` 从 `{left}{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{演示一种页面样式}
\author{Overleaf}
\date{2022年8月}
\begin{document}
\pagestyle{demostyle}
\maketitle
\newpage
\section{Introduction}
\newpage
\section{更多内容}
\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` 命令使用了可选的简短章标题 `[The short title]` ，它现在提供页眉文本，而不是长标题。

`\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{Another 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 {stuff to store}
```

它扩展了 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 `{left}{right}` 约定的后续标记数据：

```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{2022年8月}
\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 找到合适的分页点，它就会把该页的内容发送去做最终处理，并为文档中的每一页执行这（三个）个标记处理步骤。本节末尾图中引用了标注为 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`页的 *当前* ，而 `\mark` 页（正在处理的页面）则被设置为空（NULL）：先假定此页没有

节点。 *一次* 请注意，此测试会针对

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

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

该测试会应用于当前页面中包含的每个 mark 节点。上一个测试（上面的步骤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` 的值可通过下图所示的转换推导得到，其中：

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

下图展示了这三个伪代码片段如何生成 `\botmark`, `\topmark` 和 `\firstmark` 这些值列于《The TeXbook》第258页所包含的表中。

![用于配合《The TeXbook》第258页示例的转换图，展示全局 mark 变量在被赋值时的内部转换](/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://ayakaleaf-pro.ayaka.space/latex/zh-cn/shen-du-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.
