> 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/47-tex-tables-how-tex-calculates-spanned-column-widths.md).

# TeX 表格：TeX 如何计算跨列宽度

## 本文的目标

在本文中，我们探讨如何 $$\mathrm\TeX$$ 在表格包含跨越多列的条目（例如表头）（例如使用 $$\mathrm\TeX$$ 原始命令 `\omit` 和 `\span`）时计算表格列宽。以一个基础的 [“参考”表格](#reference-table) 为起点，我们通过修改其中的各个条目来创建一系列示例——这些示例都由该参考表格衍生而来——从而生成跨列的条目。通过考察这些修改带来的影响，我们可以开始理解那个 $$\mathrm\TeX$$ 用于计算跨列宽度的底层算法。

### 使用 $$\mathrm\TeX$$ 不 $$\mathrm\LaTeX$$

为了考察并解释 *如何* $$\mathrm\TeX$$ 如何决定跨列的宽度，有必要避开所有那些令人赞叹的 $$\mathrm\LaTeX$$ 表格宏包，回到最基础、最低层（原始）的表格创建命令：特别是 `\halign{...}`, `\span` 和 `\omit`. [现有的 $$\mathrm\TeX$$/$$\mathrm\LaTeX$$ 表格宏包](https://ctan.org/topic/table) 当然是必不可少的生产力工具，并提供了大量极其实用的功能，使用户能够快速使用 $$\mathrm\LaTeX$$制作出大量各式各样的表格材料。这些宏包围绕 $$\mathrm\TeX$$的底层行为提供了必要的“宏脚手架”和非常受欢迎的抽象层与隔离层，帮助处理底层复杂性。许多这样的宏包确实是复杂 $$\mathrm\TeX$$ 编程的惊人成就：我们都应当感激它们的存在，从而不必直接使用原始的 $$\mathrm\TeX$$!

实际用于计算 $$\mathrm\TeX$$ 跨列宽度的算法解释见于《 [$$\mathrm\TeX\text{book}$$](https://www.amazon.co.uk/TeXbook-Donald-E-Knuth/dp/0201134489) 第245页；而更详细的说明则见于包含 $$\mathrm\TeX$$源代码的印刷版书籍第801节（第336页）中。 [$$\mathrm\TeX\text{: The Program}$$](https://www.amazon.co.uk/Computers-Typesetting-Tex-Program-TEX/dp/0201134373)不过，对许多人来说（包括我自己），Knuth 的解释有时相当简洁扼要，而且在某些时候很难细致地跟上：带插图的示例总是非常有帮助的。

### 是的，表格很复杂

在这本书的第768节（第322页）中 [$$\mathrm\TeX\text{: The Program}$$](https://www.amazon.co.uk/Computers-Typesetting-Tex-Program-TEX/dp/0201134373)，Knuth 发表了一个有趣的评论：

> “每当 `\halign` 和 `\valign` 能够工作时，这都算得上有点奇迹，因为它们横跨了 $$\mathrm\TeX$$。”

的如此多控制结构。 [$$\mathrm\TeX\text{ in Practice}$$](https://www.amazon.co.uk/Tex-Practice-Set-Stephan-Bechtolsheim/dp/038797296X/ref=sr_1_11?s=books\&ie=UTF8\&qid=1504256043\&sr=1-11\&keywords=TeX+in+Practice) 此外，这套四卷本中的第 IV 卷 $$\mathrm\TeX$$ 经由 `\halign` 和 `\valign`.

花了不下 180 页（199–379页）来讲述在 $$\mathrm\TeX$$ 中创建表格。

### 跨列：\omit、\span 和 \multispan

如前所述，为了探索 $$\mathrm\TeX$$的列宽计算，我们需要使用“原始”的 $$\mathrm\TeX$$；这意味着原始命令与一个 $$\text{Plain }\mathrm\TeX$$ 名为 `\multispan`的宏的组合。虽然我们不会用这些命令直接展示示例表格（即，不会完整解释所有的 $$\mathrm\TeX$$ 代码），但还是值得简要说明一下：

* `\halign`：用于创建表格的两个 $$\mathrm\TeX$$ 原始命令之一。另一个是 `\valign` ，不过它的使用并不那么广泛，本文也不会讨论。
* `\omit`：一个 $$\mathrm\TeX$$ 原始命令，用来指示 $$\mathrm\TeX$$ 忽略表格条目的前导模板。
* `\span`：一个 $$\mathrm\TeX$$ 用于合并两个相邻表格条目的原始命令。
* `\multispan{n}`：一个普通的 $$\mathrm\TeX$$ 宏，用来跨越 `n` 列。

本质上，要跨列 $$\mathrm\TeX$$ 会忽略相应数量的表格前导模板，并将所需数量的表格条目合并为一个条目。 `\multispan{n}` 通过展开为一串 `\omit` 和 `\span` 所需的标记来实现跨越 `n` 列。例如， `\multispan{3}` 展开为 `\omit\span\omit\span\omit`.

## 引入我们的“参考”表格

下面是我们的参考表格，随后附有一个带注释的版本，用来解释其构造中所使用的元素：

![{{{alt}}}](/files/ab1682014456a2808ee1c485cbb919039558a02f)

通过修改我们的参考表格，我们将观察：当我们加入跨越不同列的条目时，表格的宽度以及各个单独列的宽度会发生什么变化。这个参考表格是用原始 $$\mathrm\TeX$$ 使用 `\halign{...}` 原语以及若干用于排版表格的自定义宏生成的——我们不会讨论这些宏，因为它们并不是理解示例和说明所必需的。

下面是我们参考表格的带注释版本，用来说明它的各项特征：

![{{{alt}}}](/files/27ccbfe49c89ca586b3a2a2a1cd3a64f6bdacf40)

我们的第一组示例表格，以及初始参考表格，都设置了 `\tabskip=0pt` 以便 $$\mathrm\TeX$$ 在列之间不添加任何空白：实际上，它们彼此紧挨着。这样做的原因是为了简化最初的讨论和随后的计算——在本文后面我们会重新引入非零的 `\tabskip` 间距胶，以考察它对跨列宽度计算的影响。

如注释所述，我们在所有非跨列的表格条目开头添加了一点点空白（5pt）（第一行除外）。这 5pt 的空白是所有非跨列条目总宽度的一部分（第一行除外），添加它只是为了让表格看起来不那么拥挤。

### 关于表格宽度的简短说明

该 `\halign{...}` 命令有三种形式：

* `\halign{...}`：将表格设置为 $$\mathrm\TeX$$ 计算出的任意宽度，这个宽度基于条目的大小（以及 `\tabskip` 胶）；
* `\halign to *width* {...}`：指示 $$\mathrm\TeX$$ 将表格排版为指定的 `*width*`;
* `\halign spread *amount*{...}`：把计算出的宽度调整 `*amount*`.

当 $$\mathrm\TeX$$ 使用 `\halign{...}` 排版表格时，必须先将整个表格读入内存，才能执行排版所需的各种计算。因此，除非你使用 `\halign to *width* {...}` 指定了宽度，否则在 $$\mathrm\TeX$$ 完成处理（排版）之前，你无法知道最终宽度。获得由 `\halign{...}` 生成的表格宽度的一种方法，是先把表格排版到一个 `\vbox{...}` 中（例如， `\setbox0=\vbox{\halign{...}}`），然后例如使用 `\the\wd0` 来获得宽度。

### 表格条目中不会自动换行

重要的是要注意，当 $$\mathrm\TeX$$ 正在排版一个使用 `\halign{...}` 创建的表格时，表格条目中的任何文本都不会自动进行断行：表格条目是在 *受限水平模式*下排版的——就像一个 `\hbox`一样。要启用断行，表格条目的文本需要放在一个 `\vbox{...}` 中，并在该 `\hsize` 中为 `\vbox{...}`使用合适的值。不过请注意， `\noalign{...}` 命令中的文本（一个 $$\mathrm\TeX$$ 原语）用于一个 `\halign{...}` 中，会受到 $$\mathrm\TeX$$的断行机制影响。实际上，正如其名所示， `\noalign{...}` 允许 $$\mathrm\TeX$$ “逃离” `\halign{...}` ，并在表格行之间放置材料——通常用于在表格行之间生成横线。

### 不允许：\hbox{...} 中包含 \halign{...}

你不能 *直接* 在一个 `\halign{...}` 内部排版一个 `\hbox{...}`。尝试使用 `\hbox{\halign{...}}` 会产生一个相当令人困惑的错误：

```latex
! Missing } inserted.
<插入的文本>
                }
<重新读取>
                   \halign
l.1 \hbox{\halign
```

#### 对这个错误的解释

由于外层的 `\hbox{...}` $$\mathrm\TeX$$ 处于 *受限水平模式*；随后它检测到 `\halign{...}` ，这是一个 *垂直模式* 命令。例如，如果你在一个段落中使用 `\halign{...}` ， $$\mathrm\TeX$$ 会结束该段落，处理这个 `\halign{...}` ，然后继续排版段落的其余部分。

当在一个 `\hbox{...}`， `\halign{...}` 内部使用时， $$\mathrm\TeX$$ 会试图通过强行结束当前组来逃回垂直模式： $$\mathrm\TeX$$ 报告一个“`! Missing }`”并给出错误，因为它认为你在分组用法上犯了错误。虽然右花括号（`}`）可能并没有缺失于你的 $$\mathrm\TeX$$ 代码中，但这个错误信息是 `\hbox{...}` “介入”以及 $$\mathrm\TeX$$ 根据最佳猜测采取适当措施来解决问题的结果。

## 带跨列的表示例

下面一系列表格图示提供了若干示例，用来展示跨列表格列的效果：这表明较长的表格条目会对某些列的宽度产生意想不到的结果——进而影响表格本身的宽度。我们要回答的问题是，当某个表格条目跨越多列但又“太宽而无法容纳”时， $$\mathrm\TeX$$ 会怎么做。如上所述， $$\mathrm\TeX$$ 确实会对这个列宽计算问题应用一种特定算法：下面这些示例旨在帮助我们形成对该算法工作方式的“感觉”。

### 示例表格 1

在这个示例中，我们使用 `\multispan{2}` 将第 1 列和第 2 列跨在一起，所用条目的文本是 **一个表头**:

![{{{alt}}}](/files/241803a45aab49979edc0424f868f181d08ddaf0)

#### 观察

* 这个表格的宽度与 [参考表格](#reference-table): $$327.71722\text{pt}$$.
* 跨越第 1 列和第 2 列的条目宽度是 $$81.04953\text{pt}$$ ，它小于它所跨越的那些列中条目总宽度： $$52.56676\text{pt} + 57.06679\text{pt} = 109.63355\text{pt}$$

## 示例表格 2

与 [示例表格 1](#example-table-1)一样，这个示例也使用 `\multispan{2}` 将第 1 列和第 2 列跨在一起，但这里我们使用了更长的条目，其文本是 **一个稍长一些的表头**.

![{{{alt}}}](/files/a7cbc8f5e7bb2b5728c288fce19e090fe25ca9f0)

#### 观察

如果把这个示例与我们的 [参考表格](#reference-table) 相比，我们可以看到以下几点：

* 这个表格的宽度已经从 $$327.71722\text{pt}$$ 更改为 $$374.37032\text{pt}$$增加到了：总计 $$46.6531\text{pt}$$.
* 跨越第 1 列和第 2 列的条目（$$156.28664\text{pt}$$）的宽度大于它所跨越的那些列中条目总宽度： $$52.56676\text{pt} + 57.06679\text{pt} = 109.63355\text{pt}$$。这个差值是 $$156.28664\text{pt}-109.63355\text{pt} = 46.6531\text{pt}$$ ，也就是表格宽度增加的同样幅度。
* $$\mathrm\TeX$$ 已经调整了第 2 列的宽度，以提供所需的额外空间。稍后我们会看到 $$\mathrm\TeX$$ 是如何计算第 2 列需要增加多少的。
* 第 1 列不受影响：它的宽度没有受到跨越第 1 列和第 2 列的条目的影响。

### 示例表格 3

在这个示例中，我们使用 `\multispan{3}` 将第 1 列到第 3 列跨在一起，所用条目的文本与 [示例表格 2](#example-table-2): **一个稍长一些的表头**.

![{{{alt}}}](/files/ee865966b6e6625723b94695a838a074dc46476b)

#### 观察

* 这个表格的宽度与 [参考表格](#reference-table): $$327.71722\text{pt}$$.
* 相同。$$156.28664\text{pt}$$跨越第 1 列到第 3 列的条目（ $$52.56676\text{pt} + 57.06679\text{pt} + 59.03899\text{pt} = 168.67254\text{pt}$$.
* ）的宽度小于它所跨越的三列中条目总宽度：

你开始看到某种模式出现了吗？

### 示例表格 4

与 [示例表格 3](#example-table-3)，这里我们使用 `\multispan{3}` 将第 1 列到第 3 列跨在一起，但这次使用的条目文本要长得多： **一个长得多、延伸很远的表头**.

![{{{alt}}}](/files/d92b73c4eed2c0e3f647042fa704e13ed7bbc978)

#### 观察

* 与 [参考表格](#reference-table)相比，这个表格的宽度已经从 $$327.71722\text{pt}$$ 更改为 $$465.95685\text{pt}$$：增加了 $$138.23963\text{pt}$$.
* 跨越第 1 列到第 3 列的条目宽度是 $$306.91216\text{pt}$$.
* 这三列中条目的总宽度是 $$52.56676\text{pt} + 57.06679\text{pt} + 59.03899\text{pt} = 168.67254\text{pt}$$.
* 这个长跨列条目与第 1 到第 3 列中条目之间的宽度差是 $$306.91216\text{pt}-168.67254\text{pt}=138.23962\text{pt}$$。这个差值与表格宽度增加的幅度相同（精确到小数点后 4 位！）。
* 只有第 3 列的宽度增加了：第 1 列和第 2 列都没有受到影响。

#### 一种模式浮现出来

如果我们看 [示例表格 2](#example-table-2) 和 [示例表格 4](#example-table-4) ，就会发现两种情况下都是 **跨越范围中的最后一列** 的宽度被增加，以便为跨越这些列的长条目腾出空间：

* 在 [示例表格 2](#example-table-2)：长条目跨越了第 1 列和第 2 列。第 2 列变得“被拉伸”了。
* 在 [示例表格 4](#example-table-4)：长条目跨越了第 1 列到第 3 列。第 3 列变得“被拉伸”了。

#### 第 3 列的宽度：一种算法浮现？

下面的计算更清楚地表明了 $$\mathrm\TeX$$ 正在做什么。我们已知的是：

* 跨越第 1 列到第 3 列的长条目宽度是 $$306.91216\text{pt}$$.
* 第 1 列和第 2 列中条目的总宽度是 $$52.56676\text{pt} + 57.06679\text{pt} = 109.63355\text{pt}$$.

这些值之间的差是多少？是 $$306.91216\text{pt}-109.63355\text{pt} = 197.2786\text{pt}$$ 而这就是用于第 3 列的宽度：它直接来源于 $$\mathrm\TeX$$.

### 示例表格 5

在进入更复杂的示例之前，这里再给一个“简单”的示例。这个表格包含与 [示例表格 4](#example-table-4): **一个长得多、延伸很远的表头**相同的长条目；不过这一次我们使用 `\multispan{6}` 这使得该条目可以跨越整个表格。正如你所见，最终得到的表格仍然与我们的 [参考表格](#reference-table) ($$327.71722\text{pt}$$）具有相同的宽度，这意味着没有任何列受到这个非常长的条目的影响。显然，这是因为条目（$$306.91216\text{pt}$$）的宽度小于它所跨越的所有条目总宽度： $$327.71722\text{pt}$$；即，表格的宽度。

![{{{alt}}}](/files/8299e01428cf733475ba736d00c4c19a009403a0)

### 示例表格 6：稍微复杂一些

这里，我们考察三个示例表格（6(a)–6(c)）的系列，以展示两个不同条目的效果，它们都跨到了第 5 列。 [示例表格 6(a)](#example-table-6a) 和 [示例表格 6(b)](#example-table-6b) 都展示了一个包含单个条目的表格，该条目跨越了多个列，一直到第 5 列。 [示例表格 6(c)](#example-table-6c) 把这两个跨列条目合并到同一个表格中，并提出这样一个问题：究竟是哪一个条目真正决定了第 5 列的宽度，为什么？答案将把我们带到 $$\mathrm\TeX$$.

#### 示例表格 6(a)

![{{{alt}}}](/files/b20093b29177f1f92b6b05d7ff9a0504af8e5844)

**观察**

* 与 [参考表格](#reference-table)相比，这个表格的宽度已经从 $$327.71722\text{pt}$$ 更改为 $$371.11153\text{pt}$$：增加了 $$43.39431\text{pt}$$.
* 跨越第 3 列到第 5 列的条目宽度是 $$215.06683\text{pt}$$.
* 第 3 列到第 5 列中条目的总宽度是 $$59.03899\text{pt} + 52.98344\text{pt} + 59.6501\text{pt} = 171.67253\text{pt}$$.
* 第 3 到第 5 列中所跨条目与跨列条目宽度之间的差是 $$215.06683\text{pt}-171.67253\text{pt}=43.3943\text{pt}$$：这正是表格宽度增加的精确幅度（精确到小数点后 4 位！）。

#### 示例表格 6(b)

![{{{alt}}}](/files/4502192d2ec385ba893264d0cd2d2d5a1015293f)

**观察**

* 与 [参考表格](#reference-table)相比，这个表格的宽度已经从 $$327.71722\text{pt}$$ 更改为 $$353.3233\text{pt}$$：增加了 $$25.60608\text{pt}$$.
* 跨越第 1 列到第 5 列的条目宽度是 $$306.91216\text{pt}$$.
* 第 1 列到第 5 列中条目的总宽度是 $$52.56676\text{pt} + 57.06679\text{pt} + 59.03899\text{pt} + 52.98344\text{pt} + 59.6501\text{pt} = 281.30608\text{pt}$$.
* 第 1 到第 5 列中所跨条目与跨列条目宽度之间的差是 $$306.91216\text{pt}-281.30608\text{pt}=25.60608\text{pt}$$: **note** 这是 *小于* 为 [示例 6(a)](#example-table-6a)计算出的值， $$43.3943\text{pt}$$.

#### 示例表格 6(c)

这里，我们把示例表格 [6(a)](#example-table-6a) 和 [6(b)](#example-table-6b) 合并到一个表格中：会发生什么？

![{{{alt}}}](/files/55bad35a579ae0ba7e2c9423b47752e8f6402b15)

**观察**

* 与 [参考表格](#reference-table)相比，这个表格的宽度已经从 $$327.71722\text{pt}$$ 更改为 $$371.11153\text{pt}$$：增加了 $$43.39431\text{pt}$$。我们注意到这与 [示例表格 6(a)](#example-table-6a).

#### 是什么 $$\mathrm\TeX$$ 在做什么？

为了理解 $$\mathrm\TeX$$算法及其决策过程的结果，我们注意到这个条目

![{{{alt}}}](/files/d35681a33f17c85a3b6be90af21715e6c07dec31)

超出了被 $$25.60608\text{pt}$$所跨越的条目范围；不过，这个条目

![{{{alt}}}](/files/37a4835726fb0c5750eb07d7eb97b555dcc86a09)

甚至比被跨越的条目范围还要更远：多出 $$43.3943\text{pt}$$。因此，那个条目“赢得了竞赛”，而第 5 列的宽度增加了这两者中的 **最大值** （$$43.3943\text{pt}$$）。第 5 列的宽度现在变为 $$59.6501\text{pt} + 43.3943\text{pt} = 103.0444\text{pt}$$ ，以容纳跨越第 3 列到第 5 列的条目。我们对准确“事件顺序”的描述略作简化，但结果正如我们所描述的那样。

## 重新引入一些复杂性

为了尽量降低我们（到目前为止）讨论的复杂度，我们使用了相对简单的示例来展示 $$\mathrm\TeX$$算法的原理；尤其是，我们设置了 `\tabskip=0pt`。在实际情况中，“真实世界”的表格很可能有许多跨越一系列列的条目，当然也会有非零的 `\tabskip` 间距胶——我们现在就重新回到这个话题。

### \tabskip 间距胶与跨列宽度

表格设计常常需要在列之间加入空白，当然， $$\mathrm\TeX$$ 通过一个名为 `\tabskip`的原始命令具备这一功能。这个命令可用于在以下位置放置固定或可伸缩的间距胶：

* 在表格之前（即，第 1 列左侧）；
* 在一列或多列之间；
* 在表格之后（即，最后一列右侧）。

这里有一个例子提醒我们：

![{{{alt}}}](/files/81a2bd2d5aa2da225a81eccdba8f37e7a4a8d740)

### \tabskip 间距胶如何影响跨列宽度？

存在非零的 `\tabskip` 列间间距胶为跨列条目提供了额外空间，在 $$\mathrm\TeX$$ 开始考虑是否需要增加跨越范围内最后一列的宽度之前，这些空间可以被“吸收”。

在下一个示例中，我们将用两个表格来比较跨越两列的结果。两个表格之间唯一的差别就是 `\tabskip` 间距胶的使用。

* 第一个示例使用的是我们最初的“参考”表格，如果你还记得的话，它设置了 `\tabskip=0pt`.
* 第二个示例使用的是我们 [参考表格](#reference-table) 的修改版（如上所注释），它在表格前后设置了 `\tabskip=10pt` ，但更重要的是，它在列之间设置了 `\tabskip=20pt` 。

在 *修改后的* 参考表格中，这两个跨列条目对列宽（以及表格宽度）没有影响，但它们确实会影响 *原始* [参考表格](#reference-table).

### 原始参考表格：\tabskip=0pt

这里，我们展示原始 [参考表格](#reference-table) 以及第二个表格（由我们的原始 [参考表格](#reference-table)派生而来），其中有一个条目“**测试一个更长的表头**”跨越第 1 列和第 2 列。很明显，第二个图中的第 2 列，也就是整个表格，都受到了跨列的影响。

![{{{alt}}}](/files/b3ec56ee05eca2cba65eed2a70f78cee6ea700ac)

### 修改后的参考表格：\tabskip=20pt

这里，我们展示修改后的参考表格，以及第二个表格（由我们的修改后参考表格派生而来），它也有一个条目“**测试一个更长的表头**”跨越第 1 列和第 2 列。很明显，在图中的第二个表格里，第 2 列的宽度以及整个表格都没有受到跨列的影响。在这种情况下， `\tabskip` 间距胶（`20pt`）在列之间帮助“吸收”了跨越第 1 列和第 2 列的条目中的文本所需的空间：

![{{{alt}}}](/files/eb2f8e4f999837491aaf27358d313dbd90217595)

## 的核心 $$\mathrm\TeX$$算法

希望上面提供的一系列示例有助于形成对 $$\mathrm\TeX$$ 为容纳跨列条目所做之事的“感觉”，以及 $$\mathrm\TeX$$ 在必要时如何调整每个跨列范围中 **最后** 一列的宽度。除了被跨越的单个列中的条目宽度之外，非零 `\tabskip` 间距胶的存在也是一个重要因素， $$\mathrm\TeX$$ 在决定是否需要调整任何列宽时会将其考虑在内。要记住的关键点是， $$\mathrm\TeX$$的目标是为每个跨列范围中的 **最后一列** 计算一个合适的宽度。

### 最终表格示例：跨列范围中的最后一列

在这个最终示例中，我们再次使用修改过的参考表（其中 `\tabskip` 上文讨论过的 glue 值）来推导出另一张表，其中包含若干由规则跨列的列——我们使用规则是为了让这些跨列更容易看清。

这两张表经过精心对齐，以表明在上面的表中，第 5 列之前的任何列都未受到跨列的影响。图左侧较深的绿色区域显示，两张表的第 1 至 4 列仍然完全对齐。右侧较浅绿色阴影区域则表明，只有第 5 和第 6 列受到了跨列条目的影响。

在上面的表中，跨列情况如下：

* 第 1 至第 5 列：由一个 $$400\text{pt}$$ 规则；
* 第 3 至第 5 列：由一个 $$200\text{pt}$$ 规则；
* 第 4 至第 6 列：由一个 $$250\text{pt}$$ 规则。

![{{{alt}}}](/files/09473591bda931161aa455cd8f02c1a22c51acde)

再次说明，在一系列跨列中，只有最后一列的宽度会被调整（如有需要）：中间的列不会受影响；在这里这意味着第 1 至第 4 列——当然，第 1 至第 4 列的宽度（以及中间的 `\tabskip` glue）在计算第 5 和第 6 列的调整后宽度时会被考虑在内。

### 一个……的逐步讲解 $$\mathrm\TeX$$算法

我们将以一个 *简化的* 对“……的逐步讲解$$\mathrm\TeX$$的思考过程”，即它如何计算跨列条目中的各列宽度。描述 $$\mathrm\TeX$$的算法并不总是直截了当，因此我们将采用一些“简化性的艺术处理”，以概述正在发生的事情。对所有繁杂细节感兴趣的读者可参见包含 $$\mathrm\TeX$$源代码的印刷版书籍第801节（第336页）中。 [$$\mathrm\TeX\text{: The Program}$$](https://www.amazon.co.uk/Computers-Typesetting-Tex-Program-TEX/dp/0201134373).

现实中的表格往往大量使用 `\span` 原语（例如，在 $$\mathrm\LaTeX$$ 包中）来在表格中构造多个跨列实例。为此，数据结构（在……的深处 $$\mathrm\TeX$$）会维护信息（所谓的 *跨列节点*）它们会告诉 $$\mathrm\TeX$$ 有关表项/列之间的连接（跨列）。显然， $$\mathrm\TeX$$ 必须以系统的方式应用其算法，并且需要处理整个表格才能完成最终计算——确定所有列宽、表格总宽度，以及（如有需要）表格中使用的弹性 glue 需要拉伸或收缩的量。其实并不奇怪， $$\mathrm\TeX$$ 在完全处理完 `\halign{...}` 命令之前，无法告诉你最终的表格宽度——它真的有很多工作要做！

列宽计算的起点是第 1 列，因为当然，没有任何东西可以从（并且 *横跨/进入*）第 1 列的左侧跨越。 $$\mathrm\TeX$$ 首先通过确定第 1 列的宽度，即确定哪个条目具有最大的 *自然宽度*。我们把这个最大宽度称为 $$w\_1$$如果有从第 1 列跨到第 2 列的条目，我们把该条目的宽度称为 $$w\_{12}$$ （从 1 到 2 的宽度）。此外，我们将把 `\tabskip` 第 1 列和第 2 列之间的 glue 记作 $$t\_{1}$$——注意，我们这里只考虑 *自然宽度* 的 `\tabskip` glue 的宽度，并且暂时忽略它可能具有的任何伸展或收缩成分。另外，设第 2 列中所有非跨列条目的最大自然宽度为 $$w\_2$$.

需要注意的关键点是 $$\mathrm\TeX$$ 正试图通过仅考虑那些跨列 *开始* 与第 1 列并 *结束* 于第 2 列的条目来计算第 2 列的宽度。对 $$\mathrm\TeX$$ 来说，关键考虑因素是测试 $$\max(w\_{2}, w\_{12} - (w\_1+ t\_1))$$——可能有多个条目跨越第 1 和第 2 列：有些可能很窄（小 $$w\_{12}$$），另一些则非常宽（大 $$w\_{12}$$），因此 $$\mathrm\TeX$$ 正在寻找影响最大的那个（因此 $$\max(\text{...})$$）。这里， $$w\_{12} -(w\_1+ t\_1)$$ 是一个跨越第 1 和第 2 列的条目从第 1 列“溢出”到第 2 列的量：注意 $$\mathrm\TeX$$ 正在使用第 1 列的宽度 **和** 该 `\tabskip` 间距胶（$$t\_{1}$$）在第 1 和第 2 列之间。一旦 $$\mathrm\TeX$$ 确定从第 1 列到第 2 列的任何跨列是否确实影响了第 2 列的宽度，它就会把第 2 列的宽度设为它所确定的最大值（使用上述测试）。 $$\mathrm\TeX$$ 继续沿着其他所有列推进，执行类似的测试。

最后，为完整起见，这里引用 $$\mathrm\TeX$$的列宽计算算法的核心（摘自 Knuth 对其源代码文档中关于 $$\mathrm\TeX$$):

令 $$w\_{ij}$$ 为所有跨越第……列的条目的自然宽度的最大值 $$i$$ 到 $$j$$，包括端点。最终的列宽由公式定义

$$\begin{equation\*} w\_j=\max\_{1\leq i\leq j}\biggl(w\_{ij}-\sum\_{i\leq k< j}(t\_k+w\_k)\biggr) \end{equation\*}$$

其中 $$t\_k$$ 是列之间 tabskip glue 的自然宽度 $$k$$ 和 $$k+1$$.

## 题记：使用 Overleaf 生成 SVG 图形形式的表格

所有 $$\mathrm\TeX$$ 本文中展示的表格都是在 Overleaf 平台上生成的可缩放矢量图形（SVG）文件。注释（箭头和绿色框）是通过在 Inkscape 中打开 SVG 图形后添加的——不过请注意，注释中的文字是用 $$\mathrm\TeX$$ 作为附加文本与表格一起排版的：在 Inkscape 中只添加了箭头和绿色背景。如果你想了解这是如何实现的，请继续阅读。

Overleaf 的服务器使用 $$\mathrm\TeX \text{ Live}$$ 发行版，它除了 $$\mathrm\TeX$$基于 的排版引擎之外，还提供了大量非常有用的 $$\mathrm\TeX$$相关软件工具和实用程序。其中有一个叫做 [`dvisvgm`](https://dvisvgm.de) 顾名思义，它会将 $$\mathrm\TeX$$的传统 DVI（**D**e**V**ice **I**独立）输出文件格式转换为 SVG。在其众多 [命令行选项](https://dvisvgm.de/Manpage/) `dvisvgm` 提供一个选项（`-n` 或 `--no-fonts`）可指示它将所有文本转换为 *路径* ，这意味着 SVG 图形中的文本是用线条和曲线绘制的，而不是使用实际字体和字形。这可能会增加生成的 SVG 图形文件大小，但能确保 SVG 图形具有极高的可移植性，并且几乎可以保证在任何设备上都能良好工作。

### 那么……这是怎么做到的？

在一个 [上一篇文章](https://www.overleaf.com/blog/510-using-luatex-to-run-tools-and-utilities-installed-on-overleafs-servers) 我讨论过你可以如何使用 $$\text{Lua}\mathrm\TeX$$ 来运行 Overleaf 服务器上安装的各种软件工具和实用程序——这是一种极其简单而便捷的技术。正是用这项技术生成了排版后的 SVG 图形 $$\mathrm\TeX$$ 表格，方法如下。通过主 $$\mathrm\TeX$$ 文档文件，排版每个表格的代码（使用 using `\halign`)被写入一个 `.tex` 文件。这是通过将表格代码包裹在一对命令中实现的，我把这对命令称为 `\beginscoop` 和 `\endscoop`。要实现所需结果，可能还有许多其他方法，但以下是我使用的宏定义：

```latex
\def\cc{\catcode`\#=12\relax}
\long\def\scoop#1\endscoop{\global\fulltoks={#1}\egroup}
\def\beginscoop{\global\advance\numfigs by1\relax\bgroup\cc\scoop}
```

你可以这样使用它们：

```latex
\beginscoop
\halign{...}
\endscoop
```

请注意， `\endscoop` token 仅仅用于界定 `\scoop` 的预期用法： $$\mathrm\TeX$$ 实际上会丢弃 `\endscoop` token，因此我们实际上不需要定义它（例如，通过 `\def\endscoop{...}`).

该 $$\mathrm\TeX$$ 包含在……中的代码 `\halign{...}` 会被保存到一个 `toks` 名为 `\fulltoks`的寄存器中。我遇到的一个棘手问题（在 $$\text{Lua}\mathrm\TeX$$中）是需要防止 `#` 中的字符 `\halign{...}` 在导言区被“加倍”为 `##` 在写入 `.tex` 文件时。为避免这种情况，我不得不暂时将 `\catcode`的一个记号值 `#` 字符设为 12，然后再保存 $$\mathrm\TeX$$ 代码（tokens）到 `\fulltoks` 令牌寄存器中。

下一步是把包含在……中的令牌写入 `\fulltoks` 作为一个 $$\mathrm\TeX$$ 文件——因为我使用的是 $$\text{Lua}\mathrm\TeX$$ 这证明是 *极其* 很容易，这要归功于 $$\text{Lua}\mathrm\TeX$$出色的 Lua API。简而言之，我编写了一个名为 `\writefile{...}` 的宏，它以一个令牌寄存器的名称作为参数，你希望把其中的令牌写入文件（例如， `\writefile{fulltoks}`）。在 `\writefile{...}` 宏中，我使用 Lua API 获取了……的文本表示： `\fulltoks` 令牌寄存器：

```latex
\def\writefile#1{%
\directlua{
...
...
 local p=tex.toks["#1"]
...
...
}}
```

下面是一张截图，展示了更多 `\writefile{...}` 命令设置为特定值：

[![{{{alt}}}](/files/7534cf3d0e96780f83cc1cf01fac82984f354882)](https://www.filepicker.io/api/file/ngeDmgRStGWvG044RE1A)

Lua 语言以及由……提供的 Lua API $$\text{Lua}\mathrm\TeX$$ 常常可以简化 $$\mathrm\TeX$$ 编程任务，正是因为这些有用且强大的功能，我才一直使用 $$\text{Lua}\mathrm\TeX$$ 自 2009 年左右以来一直使用——并且至今仍是这款真正出色的 $$\mathrm\TeX$$ 引擎的忠实粉丝。好了，这则 $$\text{Lua}\mathrm\TeX$$ 广告现在结束了。

既然已经如此轻松地获得了 $$\mathrm\TeX$$ 存储在……中的代码 `\fulltoks` 它会连同一些额外代码一起写入文件，使其成为一个格式正确的 $$\mathrm\LaTeX$$ 文件。接下来的步骤是：

1. 处理 `.tex` 含有我们表格的文件，使用 $$\text{pdf}\mathrm\LaTeX$$ （在 DVI 模式下），以便它排版该表格并生成一个 `.dvi` 供 `dvisvgm` 来处理。是的，你可以使用 $$\text{Lua}\mathrm\TeX$$ 来运行 $$\text{pdf}\mathrm\LaTeX$$——我再次使用了在一篇……中讨论的方法 [上一篇文章](/latex/zh-cn/shen-ru-wen-zhang/52-using-luatex-to-run-tools-and-utilities-installed-on-overleaf-s-servers.md).
2. 最后，运行 `dvisvgm` 来处理该 `.dvi` 文件，以生成排版后的 $$\mathrm\TeX$$ 表格的 SVG 图形。
3. 要获取实际的 SVG 图形，你可以从 Overleaf 下载一个 ZIP 文件——确保选择 **输入和输出文件** 选项。


---

# 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/47-tex-tables-how-tex-calculates-spanned-column-widths.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.
