TeX Tables: How TeX Calculates Spanned Column Widths
The goal of this article
In this article we explore how \omit
and \span
). Using a basic “reference” table as a starting point, we create a range of examples—derived from that reference table—by amending various entries to create spanned columns. By examining the effect of those alterations we can start to develop an understanding of the underlying algorithm that
Using not
To examine and explain how \halign{...}
, \span
and \omit
. Existing
The actual algorithm that
Yes, tables are complex
In section 768 (page 322) of the book
“It’s sort of a miracle whenever
\halign
and\valign
work, because they cut across so many of the control structures of.”
In addition, Volume IV of the four-volume book series \halign
and \valign
.
So, it is safe to observe that
Spanning columns: \omit
, \span
and \multispan
As noted, to explore \multispan
. Although we won’t be using these commands to directly illustrate our example tables (i.e, fully explaining all the
\halign
: One of two primitives (commands) for creating tables. The other is\valign
but that is not as widely used and will not be discussed in this article.\omit
: A primitive (command) that instructs to ignore a table entry’s preamble template.\span
: A primitive (command) used to combine two adjacent table entries.\multispan{n}
: A plain macro to spann
columns.
In essence, to span columns \multispan{n}
works by expanding to the sequence of \omit
and \span
tokens needed to span n
columns. For example, \multispan{3}
expands to \omit\span\omit\span\omit
.
Introducing our “reference” table
Here is our reference table followed by an annotated version which explains the elements used in its construction:
By amending our reference table we will observe what happens to the table’s width, and the width of individual columns, as we add entries that span various columns. This reference table was produced in raw \halign{...}
primitive together with a number of custom macros required to typeset the tables—we won’t discuss those macros because they are not essential to understanding the examples and explanations.
Here is an annotated version of our reference table to explain its features:
Our first set of example tables, and initial reference table, have all set \tabskip=0pt
so that \tabskip
glue to examine its effect on calculating spanned column widths.
As noted in the annotations, we have added a small amount of white space (5pt) at the start of all non-spanning table entries (except the first row). That 5pt of white space forms part of the total width of all non-spanning entries (except the first row) and was added just to make the table look a little less cluttered.
A brief note on table widths
The \halign{...}
command has three forms:
\halign{...}
: set the table to whatever width calculates, based on the size of the entries (and\tabskip
glue);\halign to width {...}
: instruct to typeset the table to a specifiedwidth
;\halign spread amount{...}
: adjust the calculated width byamount
.
When \halign{...}
it has to read the entire table into memory to perform the various calculations required to typeset it. Consequently, unless you have specified the width by using \halign to width {...}
you cannot know the final width until \halign{...}
is to first typeset the table inside a \vbox{...}
(e.g., \setbox0=\vbox{\halign{...}}
) and then, for example, use \the\wd0
to obtain the width.
No automatic line-breaking in table entries
It is important to note that when \halign{...}
any text within table entries is not automatically subjected to line-breaking: table entries are typeset in restricted horizontal mode—just like an \hbox
. To enable line-breaking, a table entry’s text needs to be enclosed inside a \vbox{...}
together with using an appropriate
value for \hsize
within that \vbox{...}
. Note, however, that text within a \noalign{...}
command (a \halign{...}
is subject to \noalign{...}
allows \halign{...}
and place material between the rows of the table—typically to produce horizontal rules between table rows.
Not allowed: \halign{...}
inside \hbox{...}
You cannot directly typeset an \halign{...}
inside an \hbox{...}
. Attempting to use \hbox{\halign{...}}
will generate a rather confusing error:
! Missing } inserted.
<inserted text>
}
<to be read again>
\halign
l.1 \hbox{\halign
An explanation of this error
Due to the enclosing \hbox{...}
\halign{...}
which is a vertical mode command. For example, if you use \halign{...}
within a paragraph, \halign{...}
and then carry on with the rest of the paragraph.
When used inside an \hbox{...}
, the \halign{...}
triggers ! Missing }
” and issues an error because it thinks you’ve made a mistake in your use of grouping. Although a right-hand brace (}
) may not be missing from your \hbox{...}
“getting in the way” and
Examples of tables with spanned columns
The following sequence of table graphics provide a range of examples to demonstrate the effect of spanning table columns: indicating that lengthy table entries can have unexpected results on the width of certain columns—and, consequently, on the width of the table itself. The question we are going to address is what does
Example table 1
In this example we use \multispan{2}
to span columns 1 and 2 with an entry whose text is A table heading:
Observations
- The width of this table is the same as the reference table:
. - The width of the entry that spans columns 1 and 2 is
which is less than the total width of the entries in the columns it spans:
Example table 2
As in Example table 1, this example also uses \multispan{2}
to span columns 1 and 2 but here we use a longer entry whose text is A slightly longer table heading.
Observations
If you compare this example to our reference table we can see the following:
- The width of this table has increased from
to : a total of . - The width of the entry that spans columns 1 and 2 (
) is greater than the total width of the entries in the columns it spans: . That difference is which is the same amount by which the table width has increased. has adjusted the width of column 2 to provide the additional space required. Later we will see how calculates the amount by which column 2 has to increase.- Column 1 is unaffected: its width has not been affected by the entry that spans columns 1 and 2.
Example table 3
In this example we use \multispan{3}
to span columns 1 to 3 with an entry whose text is the same as Example table 2: A slightly longer table heading.
Observations
- The width of this table is the same as the reference table:
. - The width of the entry that spans columns 1 to 3 (
) is less than the total width of the entries in the three columns it spans: . - None of the column widths have been affected by the entry spanning columns 1 to 3.
Are you starting to see a pattern emerging?
Example table 4
As with Example table 3, here we use \multispan{3}
to span columns 1 to 3 but this time with an entry whose text is considerably longer: A considerably longer table heading that extends a long way.
Observations
- Compared to the reference table, the width of this table has increased from
to : an increase of . - The width of the entry that spans columns 1 to 3 is
. - The total width of the entries in the three spanned columns is
. - The difference in width between the long spanning entry and the entries in columns 1 to 3 is
. The same amount (to 4 decimal places!) by which the table width has increased. - Only column 3 has had its width increased: neither column 1 or column 2 are affected.
A pattern emerges
If we look at Example table 2 and Example table 4 we can see that in both cases it is the last column in the span which had its width increased to make space for the long entry which spanned the columns:
- In Example table 2: The long entry spanned columns 1 and 2. Column 2 became “stretched”.
- In Example table 4: The long entry spanned columns 1 to 3. Column 3 became “stretched”.
The width of column 3: An algorithm emerges?
The following calculations give a clearer indication of what
- The width of the long entry spanning columns 1 to 3 is
. - The total width of the entries in columns 1 and 2 is
.
What is the difference between those values? It is
Example table 5
Before we get to a more complicated example, here is one more “simple” example. This table contains the same lengthy entry as Example table 4: A considerably longer table heading that extends a long way; however, this time we use \multispan{6}
which allows that entry to span the entire table. As you can see, the resulting table still has the same width as our reference table (
Example table 6: Slightly more complicated
Here, we look at a series of three example tables (6(a)–6(c)) to show the effect of two different entries which both span into column 5. Example table 6(a) and Example table 6(b) each show a table containing a single entry that spans several columns up to column 5. Example table 6(c) combines both spanning entries into a single table and asks the question: which entry actually determines the width of column 5, and why? The answer takes us to the essence of the algorithm used by
Example table 6(a)
Observations
- Compared to the reference table, the width of this table has increased from
to : an increase of . - The width of the entry that spans columns 3 to 5 is
. - The total width of the entries in columns 3 to 5 is
. - The difference in width between the entries spanned in columns 3 to 5 and the width of the spanning entry is
: the exact amount (to 4 decimal places!) by which the width of the table has increased .
Example table 6(b)
Observations
-
Compared to the reference table, the width of this table has increased from
to : an increase of . -
The width of the entry that spans columns 1 to 5 is
. -
The total width of the entries in columns 1 to 5 is
. -
The difference in width between the entries spanned in columns 1 to 5 and the width of the spanning entry is
: note this is less than the value calculated for Example 6(a), which was .
Example table 6(c)
Here, we combine the entries in example tables 6(a) and 6(b) into a single table: what happens?
Observation
-
Compared to the reference table, the width of this table has increased from
to : an increase of . We note this is exactly the same as Example table 6(a).
What is doing?
To understand the results of
extends beyond the entries being spanned by
extends even further beyond the entries being spanned: by
Bringing back some complexity
To minimize the complexity of our discussions (thus far) we’ve used relatively simple examples to demonstrate the principles of \tabskip=0pt
. In practice, “real world” tables are likely to have many entries that span a range of columns and, of course, will have non-zero values for the \tabskip
glue—a topic we’ll now revisit.
\tabskip
glue and spanned column widths
Table design often requires the addition of white space between columns and, of course, \tabskip
. This command can be used to put fixed or flexible glue (spacing):
- before a table (i.e., to the left of column 1);
- between one or more columns;
- after the table (i.e., to the right of the last column).
Here an example to remind ourselves:
How does \tabskip
glue affect spanned column widths?
The presence of non-zero \tabskip
glue between columns provides additional space that spanned entries can “absorb” before
In our next example we will use two tables to compare the results of spanning two columns. The only difference between the tables is the use of \tabskip
glue.
- The first example uses our original “reference” table which, if you recall, has set
\tabskip=0pt
. - The second example uses a modified version of our reference table (annotated above) which has
\tabskip=10pt
before and after the table but, more importantly, it has set\tabskip=20pt
between the columns.
Within the modified reference table the two spanned columns have no effect on the column widths (and table width) but they do affect the width of column 2 (and table width) in the original reference table.
Original reference table: \tabskip=0pt
Here, we show our original reference table together with a second table (derived from our original reference table) which has an entry “Test a longer table heading” spanning columns 1 and 2, Quite clearly, column 2 (of the second table in the diagram) and thus the whole table, are both affected by the spanned columns.
Modified reference table: \tabskip=20pt
Here, we show our modified reference table together with a second table (derived from our modified reference table) which also has an entry “Test a longer table heading” spanning columns 1 and 2, Quite clearly, within the second table in the diagram, neither the width of column 2 or the table is affected by the spanned columns. In this case, the presence of \tabskip
glue (20pt
) between the columns has helped to “absorb” the space required by the text in the entry spanning colums 1 and 2:
The essence of ’s algorithm
Hopefully, the range of examples provided above have helped develop a “sense” of what \tabskip
glue is an important factor that
Final table example: last columns in a spanned range
In this final example we once again use our modified reference table (with \tabskip
glues values discussed above) to derive another table which contains various columns spanned by rules—we have used rules to make the spans easier to see.
The two tables have been carefully aligned to show that, in the upper table, no columns prior to column 5 have been affected by the spanned columns. The darker green area to the left of the diagram shows that columns 1 to 4 of both tables still align perfectly. On the right is a lighter-green shaded area showing that only columns 5 and 6 have been affected by the spanned entries.
In the upper table, the spans are as follows:
- columns 1 to 5: spanned by a
rule; - columns 3 to 5: spanned by a
rule; - columns 4 to 6: spanned by a
rule.
Once again, the explanation is that within a series of spanned columns, only the width of the final column is adjusted (if required): intervening columns are not affected and here that means columns 1 to 4—although, of course, the width of columns 1 to 4 (and intervening \tabskip
glue) is taken into consideration when calculating the adjusted widths of columns 5 and 6.
A walkthrough of ’s algorithm
We’ll conclude with a simplified walkthrough of “
Real-world tables are often created with many uses of the \span
primitive (e.g., inside \halign{...}
command—it really has a lot of work to do!
The starting point for column-width calculations is column 1 because, of course, nothing can span from the left of (and across/into) column 1. \tabskip
glue between columns 1 and 2 as \tabskip
glue and, for now, ignoring any stretch or shrink components it might possess. Also, let the maximum natural width of all non-spanning entries in column 2 be
The key point to note is that \tabskip
glue (
And finally, just for completeness, here we quote the essence of
Let
where
Colophon: Using Overleaf to produce tables as SVG graphics
All
Overleaf’s servers use the dvisvgm
which, as its name suggests, converts dvisvgm
provides an option (-n
or --no-fonts
) that will instruct it to convert all text into paths which means that the text in SVG graphics is drawn using lines and curves rather than actual fonts and glyphs. This may increase the file size of the resulting SVG graphic but it ensures that the SVG graphics are extremely portable and almost certain to work well on any device.
So... how was it done?
In a previous article I discussed how you can use \halign
) was written to a .tex
file. This was achieved by enclosing the table code within a pair of commands which I called \beginscoop
and \endscoop
. There are likely to be many other ways to achieve the desired results but here are the macro definitions that I used:
\def\cc{\catcode`\#=12\relax}
\long\def\scoop#1\endscoop{\global\fulltoks={#1}\egroup}
\def\beginscoop{\global\advance\numfigs by1\relax\bgroup\cc\scoop}
You use them like this:
\beginscoop
\halign{...}
\endscoop
Note that the \endscoop
token merely serves to delimit the parameter of the \scoop
macro: \endscoop
token so we don’t actually need to define it (e.g., by \def\endscoop{...}
).
The \halign{...}
is saved into a toks
register called \fulltoks
. One tricky point I came across (with #
characters within the \halign{...}
preamble from being “doubled” to ##
when written out to a .tex
file. To avoid this I had to temporarily set the \catcode
of #
characters to 12 prior to saving the \fulltoks
token register.
The next step is to write the tokens contained in \fulltoks
as a \writefile{...}
which takes as its parameter the name of a token register whose tokens you want to write out to a file (e.g., \writefile{fulltoks}
). Within the \writefile{...}
macro I used the Lua API to get a textual representation of the \fulltoks
token register:
\def\writefile#1{%
\directlua{
...
...
local p=tex.toks["#1"]
...
...
}}
Here is a screenshot showing a little more of the \writefile{...}
command:
The Lua language and the Lua API provided by
Having so easily obtained the \fulltoks
it is written out to a file together with some additional code to make it into a correctly formed
- Process the
.tex
file containing our table with (in DVI mode) so that it typesets the table and generates a.dvi
file fordvisvgm
to process. Yes, you can use to run —once again I used the method discussed in a previous article. - And finally, run
dvisvgm
to process the.dvi
file to generate an SVG graphic of the typeset table. - To obtain the actual SVG graphics you can download a ZIP file from Overleaf—making sure to select the Input and Output Files option.
Overleaf guides
- Creating a document in Overleaf
- Uploading a project
- Copying a project
- Creating a project from a template
- Using the Overleaf project menu
- Including images in Overleaf
- Exporting your work from Overleaf
- Working offline in Overleaf
- Using Track Changes in Overleaf
- Using bibliographies in Overleaf
- Sharing your work with others
- Using the History feature
- Debugging Compilation timeout errors
- How-to guides
- Guide to Overleaf’s premium features
LaTeX Basics
- Creating your first LaTeX document
- Choosing a LaTeX Compiler
- Paragraphs and new lines
- Bold, italics and underlining
- Lists
- Errors
Mathematics
- Mathematical expressions
- Subscripts and superscripts
- Brackets and Parentheses
- Matrices
- Fractions and Binomials
- Aligning equations
- Operators
- Spacing in math mode
- Integrals, sums and limits
- Display style in math mode
- List of Greek letters and math symbols
- Mathematical fonts
- Using the Symbol Palette in Overleaf
Figures and tables
- Inserting Images
- Tables
- Positioning Images and Tables
- Lists of Tables and Figures
- Drawing Diagrams Directly in LaTeX
- TikZ package
References and Citations
- Bibliography management with bibtex
- Bibliography management with natbib
- Bibliography management with biblatex
- Bibtex bibliography styles
- Natbib bibliography styles
- Natbib citation styles
- Biblatex bibliography styles
- Biblatex citation styles
Languages
- Multilingual typesetting on Overleaf using polyglossia and fontspec
- Multilingual typesetting on Overleaf using babel and fontspec
- International language support
- Quotations and quotation marks
- Arabic
- Chinese
- French
- German
- Greek
- Italian
- Japanese
- Korean
- Portuguese
- Russian
- Spanish
Document structure
- Sections and chapters
- Table of contents
- Cross referencing sections, equations and floats
- Indices
- Glossaries
- Nomenclatures
- Management in a large project
- Multi-file LaTeX projects
- Hyperlinks
Formatting
- Lengths in LaTeX
- Headers and footers
- Page numbering
- Paragraph formatting
- Line breaks and blank spaces
- Text alignment
- Page size and margins
- Single sided and double sided documents
- Multiple columns
- Counters
- Code listing
- Code Highlighting with minted
- Using colours in LaTeX
- Footnotes
- Margin notes
Fonts
Presentations
Commands
Field specific
- Theorems and proofs
- Chemistry formulae
- Feynman diagrams
- Molecular orbital diagrams
- Chess notation
- Knitting patterns
- CircuiTikz package
- Pgfplots package
- Typesetting exams in LaTeX
- Knitr
- Attribute Value Matrices
Class files
- Understanding packages and class files
- List of packages and class files
- Writing your own package
- Writing your own class