> 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/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$$的源码 [$$\mathrm\TeX\text{: The Program}$$](https://www.amazon.co.uk/Computers-Typesetting-Tex-Program-TEX/dp/0201134373)的印刷版第801节（第336页）中有更进一步的细节。不过，对许多人（包括我自己）来说，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) 用不下180页（第199–379页）专门讲述如何在 $$\mathrm\TeX$$ 通过 `\halign` 和 `\valign`.

因此，可以放心地指出， $$\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 命令有三种形式：

* `\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.
<inserted text>
                }
<to be read again>
                   \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$$.

### 使用的算法。

在进入一个更复杂的示例之前，这里再给一个“简单”的示例。这个表格包含与 [示例表格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)

### &#x20;\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$$的源码 [$$\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$$ 首先通过确定哪一个条目具有最大的 *自然宽度*。把这个最大宽度称为 $$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` 令牌仅仅用于界定 `\scoop` 宏的预期用法： $$\mathrm\TeX$$ 实际上会丢弃 `\endscoop` 令牌，因此我们实际上不需要定义它（例如，通过 `\def\endscoop{...}`).

该 $$\mathrm\TeX$$ 其中包含的代码会被保存到 `\halign{...}` 会被保存到一个 `toks` 名为 `\fulltoks`的寄存器中。我遇到的一个棘手问题（在使用 $$\text{Lua}\mathrm\TeX$$时）是需要防止 `#` 其中的字符 `\halign{...}` 导言区中的字符被“加倍”为 `##` 在写入 `.tex` 文件时。为了避免这一点，我不得不暂时将 `\catcode`的记号值 `#` 字符类别设为 12，然后再保存 $$\mathrm\TeX$$ 代码（令牌）到 `\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 语言以及由 $$\text{Lua}\mathrm\TeX$$ 提供的 Lua API 往往可以简化 $$\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-du-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://ayakaleaf-pro.ayaka.space/latex/zh-cn/shen-du-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.
