# halign to <dimension> does not grow

I would like to set the width of a halign to 400pt:

\tracingonline=1
\def\tablerule{\noalign{\hrule}}
\newcount\posno\posno=1
\tabskip=3pt
\halign to 400pt{#\hfil&#\hfil&#\hfil&#\hss\cr
\tablerule
{\bf Pos.} & {\bf Anz.} & {\bf Beschreibung} & {\bf Seriennummer}\cr\tablerule
\pos & 1 St. & Abcdefg & 123/12/12-1234567\cr\tablerule
\pos & 1 St. & Abcdefg & 123/12/12-1234567\cr\tablerule
}
\bye


But the contents of the halign does not grow to 400pt, although I have used several \hfil commands. TeX warns about the underfull \hbox.

The TeXbook says:

Therefore ‘\halign to \hsize’ will do nothing if the tabskip glue has no stretchability or shrinkability, except that it will cause TeX to report an underfull or overfull box.

I seems to me that this happens here. But I do not understand why, because in my template line is stretchablility.

• You need that \tabskip glue is flexible. – egreg Feb 18 at 13:26

In an \halign, for a given column, TeX measures the natural width of each cell in the column and typesets the cell contents in an implicit \hbox to m where m is the maximum natural width found for all cells in the column. This is where the \hfil and \hss in your alignment preamble play a role: to fill each cell to the maximum natural width within the column. This process is repeated for each column.

So, these skips never get to expand over the whole width of your text body. Their stretchability or shrinkability is only used to fill boxes whose width is a column width. The d dimen that you give as \halign to d is considered in quite a different context: once the boxing of each cell has been done as previously mentioned, each row of the \halign will be set to fill width d. But the skips in already-boxed column contents can't help for this: once glue has been set inside a box, the skips inside it don't stretch nor shrink anymore. So, in order to help the \halign fill the d dimen you imposed, you need to provide stretchable glue elsewhere. This is where \tabskip can help. If you allow \tabskip to stretch, the lines will have the dimension you asked:

\def\tablerule{\noalign{\hrule}}
\newcount\posno\posno=1
\tabskip=3pt plus 1fil
\halign to 400pt{#\hfil&#\hfil&#\hfil&#\hfil\cr
\tablerule
{\bf Pos.} & {\bf Anz.} & {\bf Beschreibung} & {\bf Seriennummer}\cr\tablerule
\pos & 1 St. & Abcdefg & 123/12/12-1234567\cr\tablerule
\pos & 1 St. & Abcdefg & 123/12/12-1234567\cr\tablerule
}
\bye


Note that the { and } in places such as {\bf Pos.} & {\bf Anz.} & are not necessary, because each cell contents in an alignment is implicitly set inside a group. The space token I added after \advance\posno by 1 is important in case more digits follow \pos. It ensures the 〈number〉 is terminated for TeX.

If you want to remove the space before the first and after the last column, you can modify \tabskip before \halign and inside the alignment preamble. In order to understand how each intercolumn space is determined (or the before-first, or after-last column space), consider the following quote the TeXbook, p. 238:

The poultry example just given used the same \tabskip glue everywhere, but you can vary it by resetting \tabskip within the preamble. The \tabskip glue that is in force when TeX reads the { following \halign will be used before the first column; the \tabskip glue that is in force when TeX reads the & after the first template will be used between the first and second columns; and so on. The \tabskip glue that is in force when TeX reads the \cr after the last template will be used after the last column.

Thus, in order to suppress the space before the first and after the last column while keeping infinitely-stretchable space between the columns, one can do:

\def\tablerule{\noalign{\hrule}}
\newcount\posno\posno=1

\hsize=450pt                    % whatever

\hrule width 400pt              % for comparison with the table
\vskip 5pt
\noindent\rlap{\vdots}\hskip 400pt\llap{\vdots}\par
\vskip 5pt

\begingroup
\tabskip=0pt\relax % \relax not necessary here, because \halign follows
\halign to 400pt{#\hfil \tabskip=3pt plus 1fil&
#\hfil&
#\hfil&
#\hfil \tabskip=0pt\cr
\tablerule
\bf Pos. & \bf Anz. & \bf Beschreibung & \bf Seriennummer\cr\tablerule
\pos & 1 St. & Abcdefg & 123/12/12-1234567\cr\tablerule
\pos & 1 St. & Abcdefg & 123/12/12-1234567\cr\tablerule
}
\endgroup             % restore \tabskip value in force before the \begingroup

\bye


Note: for typographical reasons, you may want to use less rules and add vertical kerns or skips between the rows using \noalign. Here is a suggestion based on values from the booktabs LaTeX package:

\newskip\abovetopsep
\newskip\aboverulesep
\newskip\belowrulesep
\newskip\belowbottomsep

\newdimen\heavyrulewidth
\newdimen\lightrulewidth

\abovetopsep=0.0pt
\aboverulesep=1.72218pt
\belowrulesep=2.79857pt
\belowbottomsep=0.0pt
\heavyrulewidth=0.80002pt       % that's what booktabs.sty does...
\lightrulewidth=0.50003pt       % ditto

\def\tabletoprule{%
\noalign{\vskip\abovetopsep
\hrule height \heavyrulewidth
\vskip\belowrulesep}%
}
\def\tablemidrule{%
\noalign{\vskip\aboverulesep
\hrule height \lightrulewidth
\vskip\belowrulesep}%
}
\def\tablebottomrule{%
\noalign{\vskip\aboverulesep
\hrule height \heavyrulewidth
\vskip\belowbottomsep}%
}

\newcount\posno
\posno=1

\hsize=450pt                    % whatever

\begingroup
\tabskip=0pt\relax % \relax not necessary here, because \halign follows
\halign to 400pt{\strut
#\hfil \tabskip=3pt plus 1fil&
#\hfil&
#\hfil&
#\hfil \tabskip=0pt\cr
\tabletoprule
\bf Pos. & \bf Anz. & \bf Beschreibung & \bf Seriennummer\cr
\tablemidrule
\pos & 1 St. & Abcdefg & 123/12/12-1234567\cr
\pos & 1 St. & Abcdefg & 123/12/12-1234567\cr
\tablebottomrule
}
\endgroup             % restore \tabskip value in force before the \begingroup

\bye


• I made several improvements to the answer, you may want to reread it. – frougon Feb 18 at 17:15