> 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-tw/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$$ 表格套件，並回到基本的、低階的（primitive）表格建立命令：尤其是 `\halign{...}`, `\span` 以及 `\omit`. [現有的 $$\mathrm\TeX$$/$$\mathrm\LaTeX$$ 表格套件](https://ctan.org/topic/table) 當然是不可或缺的生產力工具，並提供大量極其有用的功能，使使用者能夠快速地利用 $$\mathrm\LaTeX$$產生各式各樣的表格式內容。那些套件提供了圍繞著 $$\mathrm\TeX$$的底層行為所建構的必要「巨集鷹架」，而其開發者提供了非常受歡迎的抽象化與隔離層，負責處理底層的複雜性。那些套件中的許多確實是複雜 $$\mathrm\TeX$$ 程式設計的驚人成就：我們都應該感謝它們存在，讓我們不必使用原始的 $$\mathrm\TeX$$!

實際上用來計算跨欄欄寬的演算法 $$\mathrm\TeX$$ 在 Knuth 的 [$$\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) 有不下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/64624340d7cb901667f4a8f4b382477d64ff7d94)

藉由修改參考表格，我們將觀察當加入跨越不同欄的項目時，表格的寬度以及各個欄的寬度會發生什麼變化。這個參考表格是以原始 $$\mathrm\TeX$$ 使用 `\halign{...}` 指令與多個自訂巨集所產生，這些巨集是排版表格所需——我們不會討論那些巨集，因為它們對理解這些範例與說明並非必要。

以下是我們參考表格的加註版本，以說明其特色：

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

我們的第一組範例表格，以及最初的參考表格，都將 `\tabskip=0pt` 設為 $$\mathrm\TeX$$ 使 `\tabskip` 不會在欄與欄之間加入任何空白：實際上，它們彼此都緊貼在一起。這樣做的原因是為了簡化最初的討論與隨後的計算——在本文後面，我們會重新引入非零的

\tabskip

### 關於表格寬度的簡短說明

該 `\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{...}` 一樣。要啟用斷行，表格項目的文字需要包在一個 `，並在該` 中使用適當的 `\vbox{...}`值。請注意，不過，位於 `\noalign{...}` 命令（ $$\mathrm\TeX$$ 原始指令）中所使用的文字，在 `\halign{...}` 中會受到 $$\mathrm\TeX$$的斷行影響。實際上，正如其名稱所暗示的， `\noalign{...}` 允許 $$\mathrm\TeX$$ 從 `\halign{...}` 中「脫離」，並將材料放置在表格各列之間——通常是為了在表格列之間產生水平線。

### 不允許：\halign{...} 在 \hbox{...} 內

你不能 *直接* 排版一個 `\halign{...}` 於一個 `\hbox{...}`內。嘗試使用 `\hbox{\halign{...}}` 會產生一個相當令人困惑的錯誤：

```latex
! 遺漏了 }。
<插入的文字>
                }
<待再次讀取>
                   \halign
l.1 \hbox{\halign
```

#### 此錯誤的說明

由於外層的 `\hbox{...}` $$\mathrm\TeX$$ 位於 *建立的表格時，任何表格項目中的文字都不會自動進行斷行：表格項目是以*中；接著它偵測到 `\halign{...}` 這是一個 *垂直模式* 命令。例如，如果你在段落中使用 `\halign{...}` ， $$\mathrm\TeX$$ 會終止該段落、處理 `\halign{...}` ，然後繼續處理段落的其餘部分。

當在一個 `\hbox{...}`中使用時， `\halign{...}` 會觸發 $$\mathrm\TeX$$ 嘗試透過強制關閉目前的群組來逃回垂直模式： $$\mathrm\TeX$$ 會回報一個「`! 遺漏了 }`」並產生錯誤，因為它以為你在群組使用上犯了錯。雖然右大括號（`}`）可能並未遺漏於你的 $$\mathrm\TeX$$ 程式碼中，但這個錯誤訊息是 `\hbox{...}` 「擋路」以及 $$\mathrm\TeX$$ 以其「最佳猜測」來採取適當的解決方式之症狀。

## 含有跨欄的表格範例

以下一系列表格圖形提供了多種範例，以展示跨欄的效果：這表示冗長的表格項目可能對某些欄的寬度——進而對表格本身的寬度——造成意想不到的結果。我們要處理的問題是，當某個表格項目跨越多個欄但「寬得放不下」時， $$\mathrm\TeX$$ 會怎麼做。如上所述， $$\mathrm\TeX$$ 確實針對這個欄寬計算問題套用了一個特定的演算法：以下範例旨在幫助建立對該演算法運作的「感覺」。

### 範例表格 1

在這個範例中，我們使用 `\multispan{2}` 來跨越第1欄與第2欄，項目文字為 **表格標題**:

![{{{alt}}}](/files/972af55efff006740a881b92169dc2074d5cb858)

#### 觀察

* 這個表格的寬度與 [參考表格](#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/65bce4a25be26ae0acf7929f7bfc1b87911d1c87)

#### 觀察

如果你將這個範例與我們的 [參考表格](#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/e03f919e75cb2557bcfe4b9bc1de5744b33e3391)

#### 觀察

* 這個表格的寬度與 [參考表格](#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/1f33d36f37d5997fb4e26ab0c55b2193dbbf12c7)

#### 觀察

* 與 [參考表格](#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/8e42455464d2de5a4df056d310ac90f08d66c845)

### 範例表格 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/aa0d06ba660c03daba9935fe94ad1d0b3741a85d)

**觀察**

* 與 [參考表格](#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/e2a165c58f401268887395efa6cbb759e9bff786)

**觀察**

* 與 [參考表格](#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}$$: **注意** 這是 *小於* 為 [範例 6(a)](#example-table-6a)，即 $$43.3943\text{pt}$$.

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

在這裡，我們將範例表格 [6(a)](#example-table-6a) 以及 [6(b)](#example-table-6b) 中的項目合併成一個表格：會發生什麼事？

![{{{alt}}}](/files/475955f40b2f7f74b1fccdd96c250ec006e46cce)

**觀察**

* 與 [參考表格](#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/723205933fe1091605dbd27f57999c2a400f2cb0)

超出了 $$25.60608\text{pt}$$所跨越的項目；然而，這個項目

![{{{alt}}}](/files/434364dfd5ff22236a144bb94be927a662c929ee)

又進一步超出了所跨越的項目：超出 $$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/7ed68f3aae2f279f60ef0e1af1f9f651f5f4c440)

### \tabskip 膠如何影響跨欄欄寬？

非零的 `\tabskip` 欄間膠提供了額外空間，跨欄項目在 $$\mathrm\TeX$$ 需要考慮增加跨欄中最後一欄的寬度之前，可以先「吸收」這些空間。

在下一個範例中，我們將使用兩個表格來比較跨越兩欄的結果。兩個表格之間唯一的差異是使用了 `\tabskip` 膠。

* 第一個範例使用我們原本的「參考」表格，若你還記得，它將 `\tabskip=0pt`.
* 第二個範例使用我們 [參考表格](#reference-table) 的修改版（如上加註），其 `\tabskip=10pt` 已設於表格前後，但更重要的是，它在欄與欄之間設定了 `\tabskip=20pt` 。

在 *修改後的* 參考表格中，這兩個跨欄項目對欄寬（與表格寬度）沒有影響，但它們會影響 *原始* [參考表格](#reference-table).

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

在這裡，我們展示原始的 [參考表格](#reference-table) 以及第二個表格（衍生自我們原始的 [參考表格](#reference-table)) 其中有一個項目「**測試較長的表格標題**」跨越第 1 與第 2 欄。很明顯，第 2 欄（圖中第二個表格中的第 2 欄），因而整個表格，都受到跨欄項目的影響。

![{{{alt}}}](/files/1c8a0237d420825da8daa9af4c95cd0ca5af0643)

### 修改後的參考表：\tabskip=20pt

在這裡，我們展示修改後的參考表，以及由它衍生出的第二個表格；該表格也有一個項目「**測試較長的表格標題**」跨越第 1 與第 2 欄。很明顯，在圖中的第二個表格內，第 2 欄的寬度或整個表格都不受跨欄項目的影響。在這種情況下， `\tabskip` 膠料（`20pt`）介於各欄之間，幫助「吸收」了跨越第 1 與第 2 欄的項目所需的空間：

![{{{alt}}}](/files/47ba00e9f15109dbd3c2673727dabb8db789fd3e)

## ……的精髓 $$\mathrm\TeX$$的演算法

希望以上提供的各個範例，已幫助你培養出對於 $$\mathrm\TeX$$ 如何處理跨欄項目以及 $$\mathrm\TeX$$ 如何在必要時調整每個跨欄範圍內的 **最後** 一欄的寬度，有了「感覺」。除了個別欄位中被跨越之項目的寬度之外，存在非零 `\tabskip` 膠料也是 $$\mathrm\TeX$$ 在決定是否需要調整任何欄寬時所會納入考量的重要因素。要記住的關鍵是， $$\mathrm\TeX$$的目標是為每個跨欄範圍內的 **最後一欄** 計算出合適的寬度。

### 最終表格範例：跨欄範圍中的最後幾欄

在這個最後的範例中，我們再次使用修改後的參考表（包含上文討論的 `\tabskip` 膠料值）來推導另一個表格；此表格包含由規則跨越的各種欄位——我們使用規則來讓跨欄更容易看見。

這兩個表格經過仔細對齊，以顯示在上方表格中，第 5 欄之前沒有任何欄位受到跨欄欄位的影響。圖左側較深的綠色區域顯示兩個表格的第 1 至第 4 欄仍然完全對齊。右側則是較淺的綠色陰影區域，顯示只有第 5 與第 6 欄受到跨欄項目的影響。

在上方表格中，跨欄情況如下：

* 第 1 至第 5 欄：由一條 $$400\text{pt}$$ 規則；
* 第 3 至第 5 欄：由一條 $$200\text{pt}$$ 規則；
* 第 4 至第 6 欄：由一條 $$250\text{pt}$$ 規則。

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

再次說明，關鍵在於：在一連串跨欄欄位中，只有最後一欄的寬度會（如有需要）被調整：中間的欄位不受影響；而在這裡，這表示第 1 至第 4 欄——雖然當然，在計算第 5 與第 6 欄調整後的寬度時，也會把第 1 至第 4 欄（以及中間的 `\tabskip` 膠料）納入考量。

### 對 $$\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).

的書中第 801 節（第 336 頁）。 `\span` 原始命令（例如在 $$\mathrm\LaTeX$$ 套件）來建立表格中多個跨欄欄位的實例。為了管理這些資訊，資料結構（在 $$\mathrm\TeX$$的深處）會維護資訊（所謂的 *跨欄節點*）用來告訴 $$\mathrm\TeX$$ 有關表格項目／欄位之間的連結（跨欄）。很明顯， $$\mathrm\TeX$$ 必須以系統化的方式套用其演算法，並需要處理整個表格才能完成最終計算——也就是決定所有欄寬、表格總寬，以及必要時調整表格中所用彈性膠料需要拉伸或收縮的量。這並不令人意外，因為 $$\mathrm\TeX$$ 在完全處理完 `\halign{...}` 命令之前，無法告訴你最終的表格寬度——它真的有很多工作要做！

欄寬計算的起點是第 1 欄，因為當然，不可能有任何內容能夠從（且 *跨越／進入*）第 1 欄的左側。 $$\mathrm\TeX$$ 會先找出第 1 欄的寬度，方法是找出哪一個項目的 *自然寬度*最大。讓我們把那個最大寬度稱為 $$w\_1$$；如果有些項目跨越第 1 欄到第 2 欄，我們把該項目的寬度稱為 $$w\_{12}$$ （第 1 到第 2 欄的寬度）。此外，我們將第 1 與第 2 欄之間的 `\tabskip` 膠料記為 $$t\_{1}$$——注意，我們現在只考慮該 *自然寬度* 膠料的 `\tabskip` ，並暫時忽略它可能具有的任何伸展或收縮成分。此外，第 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 膠料的自然寬度 $$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$$ 文件檔案中，每個表格的排版程式碼（使用 `\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`中的 `#` 字元的 $$\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/1e5fae050b47a8015145c28eff0320e03cb0142e)](https://www.filepicker.io/api/file/ngeDmgRStGWvG044RE1A)

Lua 語言以及 $$\text{Lua}\mathrm\TeX$$ 提供的 Lua API，經常能簡化 $$\mathrm\TeX$$ 程式設計任務，而正因為這些有用而強大的功能，我自 \~2009 年以來一直使用 $$\text{Lua}\mathrm\TeX$$ ——並且仍然是這個真正奇妙的 $$\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-tw/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-tw/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.
