# Centering contents after \unvbox

Trying to build a complex macro, I managed to compute what I need in a vbox. Now, I want to \unvbox this box to allow page breaks, and would like each element it contains to be centered.

I can't find a way to add some glue on the left and right of each element. Is it at all possible? Any alternative?

Edit: typical boxes I'd like to handle are of this kind:

\vbox{
\halign{\hfil#&\hfil#\hfil&#\hfil\cr
aa & b & cc\cr
d & eee & f\cr}
}


(I don't really want to modify them because I need to access their \wd at some point).

-
It all depends on how the \vbox has been built. Can you provide some code? –  egreg Jul 21 '11 at 16:02
Added some some typical example. In practice, the contents aa, b… are other big boxes. –  Stéphane Gimenez Jul 21 '11 at 16:40
Can you provide a full minimal example? What do you want to reach? –  Marco Daniel Jul 21 '11 at 16:49
The \halign is formed, at its outer level, by hboxes all of the same width. It's not clear what you want to achieve. What do you want to center? –  egreg Jul 21 '11 at 18:00

# First method

This code is Plain TeX, but can be used also in LaTeX; the macro \centervbox takes as argument a box register number and outputs the same box, but centered with respect to the margins.

\catcode\@=11
\def\centervbox#1{\begingroup\global\setbox\@ne=\box\voidb@x
\setbox\z@=\copy#1\relax\docentervbox}
\def\docentervbox{%
\setbox\z@=\vbox{\unvbox\z@ \setbox\tw@=\lastbox
\skip@=\lastskip\unskip
\global\setbox\@ne=\vbox{
\vskip\skip@
\hbox to\hsize{\hfil\box\tw@\hfil}
\unvbox\@ne}}%
\ifdim\ht\z@=\z@
\def\next{\unvbox\@ne\endgroup}%
\else
\let\next\docentervbox
\fi
\next}
\catcode\@=12

\vsize=5\baselineskip

\setbox4=\vbox{
\halign{\hfil#&\hfil#\hfil&#\hfil\cr
aa & b & cc\cr
aa & b & cc\cr
aa & b & cc\cr
aa & b & cc\cr
aa & b & cc\cr
aa & b & cc\cr
d & eee & f\cr}
}

\centervbox{4}

\bye


In the example I've used box register 4, but if you say \newbox\mybox and then

\setbox\mybox=\vbox{...}


then you can say \centervbox\mybox. The box register remains untouched (if it's not box register 1, but using this would be a very bad thing).

We assume that there is already a box built as a \vbox in a box register, that is something like \setbox\mybox=\vbox{...} and that this box contains only hboxes and glue between them. The procedure is to copy this box into a scratch box (number 0); we reserve also box register 1, on which we'll act always globally, first of all ensuring it's void. Then we start the recursion: at each step we retrieve the bottom hbox from box 0 and transfer it into box 1; we also transfer the interline glue. However, the retrieved box is put inside a box as wide as \hsize and centered in it.

The recursion ends when the height of box 0 is zero. At that point we \unvbox box register 1.

# Second method

An alternative way to do the same is to typeset twice the alignment, first gathering its natural width and then with the desired preamble:

\newdimen\alignlen \newtoks\preamble
\long\def\centeralign#1#2{\par\preamble{#1}%
\setbox0=\vbox{\tabskip=0pt\halign{&##\cr#2\crcr}}%
\alignlen=\wd0
\begingroup\tabskip=0pt plus 1fil
\halign to\hsize{\tabskip=0pt \the\preamble\tabskip=0pt plus 1fil\cr
#2\crcr}\endgroup}

\bigskip

\centeralign{\hfil#&\hfil#\hfil&#\hfil}
{aa & b & cc\cr
aa & b & cc\cr
aa & b & cc\cr
aa & b & cc\cr
aa & b & cc\cr
aa & b & cc\cr
d & eee & f\cr}


This requires a slightly different syntax, but not so complicated. First the alignment is typeset in a box with a standard preamble, nothing more than "print each entry". The length register \alignlen is loaded with the width. Then the alignment is typeset with infinite glue on each side, with the desired preamble. Since the \halign is outside any box, it can be split across pages. This method has the advantage that \noalign commands can be put after \cr, stating penalties and vertical skips.

If the box to center is an \halign we can use a different method. First of all we typeset the \halign inside a box with a trivial preamble &#\cr means "any number of columns with left alignment"; this will give the possibility to measure the natural width of the alignment, as requested.

Then we typeset the alignment with the requested preamble, but with infinitely stretchable \tabskip glue at both sides. The preamble had been saved in the token register \preamble, we simply augment it by adding \tabskip=0pt which will hold for all intercolumn spacing, at the start and the infinite glue specification for the spacing at the right.

# Third method

It's possible to carry in the newly formed box also kerns and penalties, in addition to skips and boxes. No other items can come along.

\catcode\@=11
\newif\ifboxended
\def\centervbox#1{\begingroup\global\setbox\@ne=\box\voidb@x
\global\boxendedfalse
\setbox\z@=\copy#1\relax\docentervbox}
\def\docentervbox{%
\setbox\z@=\vbox{\unvbox\z@
\ifcase\lastnodetype
% char node (can't remove)
\or
% hlist node
\setbox\tw@=\lastbox
\def\next{%
\global\setbox\@ne=\vbox{
\hbox to\hsize{\hfil\box\tw@\hfil}\unvbox\@ne}}%
\or
% vlist node
\setbox\tw@=\lastbox
\def\next{%
\global\setbox\@ne=\vbox{
\hbox to\hsize{\hfil\box\tw@\hfil}\unvbox\@ne}}%
\or
% rule node (can't remove)
\or
% ins node (can't remove)
\or
% mark node (can't remove)
\or
\or
% ligature node (can't happen)
\or
% disc node (can't happen)
\or
% whatsit node (can't remove)
\or
% math node (can't remove)
\or
% glue node
\skip@=\lastskip\unskip
\def\next{\global\setbox\@ne=\vbox{\vskip\skip@\unvbox\@ne}}%
\or
% kern node
\dimen@=\lastkern\unkern
\def\next{\global\setbox\@ne=\vbox{\kern\dimen@\unvbox\@ne}}%
\or
% penalty node
\count@=\lastpenalty\unpenalty
\def\next{\global\setbox\@ne=\vbox{\penalty\count@\unvbox\@ne}}%
\or
% unset node (can't happen)
\or
% math mode node (can't remove)
\else
% empty list
\def\next{\global\boxendedtrue}
\fi
\next}
\ifboxended
\def\next{\unvbox\@ne\endgroup}
\else
\let\next\docentervbox
\fi
\next}
\catcode\@=12


The list of node types can be found in the documentation of e-TeX (texdoc etex). The special case -1 is when the list is empty, which we use to end the recursion.

-
Awesome! This works like a charm! I'll share my motivations as soon as I clean up my code a bit. –  Stéphane Gimenez Jul 21 '11 at 21:43
Give a look at the second method. –  egreg Jul 21 '11 at 22:42
Here is the motivation gist.github.com/1099872 (Compile it for explainations). And I think I'm forced to stick with the first method. Thanks again! –  Stéphane Gimenez Jul 22 '11 at 17:09
It is possible to add the \count@=\lastpenalty\unpenalty trick to your \centervbox macro! Seems to work fine. Also, is \ht0=0 really equivalent to "\box0 is empty"? –  Stéphane Gimenez Jul 22 '11 at 22:02
One should cycle through skips and penalties, in order to remove them. There is e-TeX code that can examine the last node type so as to take appropriate actions: \ifcase\lastnodetype...\fi. I'll try to supply the necessary macros. The box never becomes really void, but there's the special type for \lastnodetype when the list is empty. –  egreg Jul 22 '11 at 22:10

\halign is plain tex coding, so adding glue to the right and left of each element is best done in the plain style. thus

\vbox{
aa & b & cc\cr
d & eee & f\cr}
}


would do the job; replace \quad by any appropriate fixed glue amount. it need not be the same amount in all positions. traditionally, half of the glue is put in on either side of the &, but it should work as well all in one place since it looks like you want the first column right-aligned, the second column centered, and the third column left-aligned.

-
Thanks, sorry if my question was unclear, the main point was to break the top level vbox so the contents can be split across pages. –  Stéphane Gimenez Jul 21 '11 at 21:47