11

Here are some cases where when there is stuff inside a vbox, the baselineskips are sometimes there and sometimes not. Or maybe it's something else that is producing the differences?

\setbox0\vbox{% halign is vertical mode material?
  \halign{& \strut$#\hfil$\cr
    a& b& c\cr
    d& e& f\cr
    g& h& i\cr}}% each cell expands out to \hbox{...
\setbox1\hbox{% valign is horizontal mode material?
  \valign{& \hbox{\strut$#\hfil$}\cr
    a& d& g\cr
    b& e& h\cr
    c& f& i\cr}}% each cell expands out to \vbox{\hbox{...
\hbox{\box0\box1}
\vbox{When there is just text inside a \vbox{vbox,}
  it uses the whole hsize and indents}
\vbox{\hbox{except when}\hbox{it consists}\hbox{of consecutive}\hbox{hboxes}}
\bye

enter image description here

I've run into this on numerous times but never really understood what is happening or how can I fix it.

(The question Why is \baselineskip not inserted before an \unvbox? might have something to do with this?)

morbusg
  • 25,490
  • 4
  • 81
  • 162

2 Answers2

9

Let's simplify your example. The main point is here

\hsize=6cm % just to get a two line paragraph
\vbox{When there is just text inside a vbox
  it uses the whole hsize and indents}
\vbox{\hbox{except when}\hbox{it consists}\hbox{of consecutive}\hbox{hboxes}}

A \vbox has depth equal to the depth of the last item inside it. Thus both vboxes have depth zero, because by chance the second line in the first box has no descender and the same holds for \hbox{hboxes}.

All the rest contributes to the height of the \vbox. Thus, when TeX wants to pile the two vboxes over each other, it computes the interline glue. But, clearly, the height of the second \vbox is larger than \baselineskip, so the rule imposes TeX to insert \lineskip glue (default value 1pt).

Let's see a picture obtained by setting \lineskip to 20pt, just to see that it's inserted:

enter image description here

How do we solve the problem? Here's the answer:

\hsize=6cm % just to get a two line paragraph
\vbox{When there is just text inside a vbox
  it uses the whole hsize and indents\strut}
\nointerlineskip
\vbox{\hbox{\strut except when}\hbox{it consists}\hbox{of consecutive}\hbox{hboxes}}

enter image description here

Just inhibit the \baselineskip-\lineskiplimit-\lineskip computations with \nointerlineskip and ensure that the two lines are correctly separated by using struts. This won't work, though, if either the \vbox above has large depth or the first box in the \vbox below has large height.


Let's tackle now the \halign and \valign problem. The input

\setbox0=\vbox{% halign is vertical mode material
  \halign{& \strut$#\hfil$\cr
    a& b& c\cr
    d& e& f\cr
    g& h& i\cr}}% each cell expands out to \hbox{...

is equivalent to do

\setbox0=\vbox{
  \hbox{\hskip0pt
        \hbox to X{\strut$a\hfil$}\hskip0pt
        \hbox to X{\strut$b\hfil$}\hskip0pt
        \hbox to X{\strut$c\hfil$}\hskip0pt}
  \hbox{\hskip0pt
        \hbox to X{\strut$d\hfil$}\hskip0pt
        \hbox to X{\strut$e\hfil$}\hskip0pt
        \hbox to X{\strut$f\hfil$}\hskip0pt}
  \hbox{\hskip0pt
        \hbox to X{\strut$g\hfil$}\hskip0pt
        \hbox to X{\strut$h\hfil$}\hskip0pt
        \hbox to X{\strut$i\hfil$}\hskip0pt}
}

where X stands for the maximum width of the cells (computed by TeX) and \hskip0pt comes from the default value of \tabskip.

This has the same property as before: the depth of the box is the depth of the last box inside it (so it's the depth of the strut) and the rest contributes to the height. The reference point is the same as the last box reference point, so the math mode "g", "h" and "i" sit on the baseline determined by this reference point.

What about the \valign? The input

\setbox2=\hbox{% valign is horizontal mode material
  \valign{& \hbox{\strut$#\hfil$}\cr
    a& d& g\cr
    b& e& h\cr
    c& f& i\cr}}% each cell expands out to \vbox{\hbox{...
\hbox{\box0\box1}

is equivalent to

\setbox2=\hbox{%
  \vbox{\vskip0pt
        \vbox to X{\hbox{\strut$a\hfil$}}\vskip0pt
        \vbox to X{\hbox{\strut$d\hfil$}}\vskip0pt
        \vbox to X{\hbox{\strut$g\hfil$}}\vskip0pt}%
  \vbox{\vskip0pt
        \vbox to X{\hbox{\strut$b\hfil$}}\vskip0pt
        \vbox to X{\hbox{\strut$e\hfil$}}\vskip0pt
        \vbox to X{\hbox{\strut$h\hfil$}}\vskip0pt}%
  \vbox{\vskip0pt
        \vbox to X{\hbox{\strut$c\hfil$}}\vskip0pt
        \vbox to X{\hbox{\strut$f\hfil$}}\vskip0pt
        \vbox to X{\hbox{\strut$i\hfil$}}\vskip0pt}%
  }

where X is the maximum height of the cells (in this case it's \ht\strutbox). But here the "bad thing" happens! The reference point is determined by the last item, which is glue. So the bottom of the $g$ sits on the line determined thereby.

Just do

\noindent\rlap{\vrule height0.4pt width 4cm}\vrule\box2

(where in place of X you write \ht\strutbox) and you'll realize the fact.

Here it is: the first is the \valign, the second the "equivalent" box.

enter image description here

egreg
  • 1,121,712
5

A version of your input with better alignment is

\setbox0\vbox{% halign is vertical mode material?
  \halign{& \strut$#\hfil$\cr
    a& b& c\cr
    d& e& f\cr
    \strut g& h& i\cr}\kern0pt}% each cell expands out to \hbox{...
\setbox2\hbox{% valign is horizontal mode material?
  \valign{& \hbox{\strut$#\hfil$}\cr
    a& d& g\cr
    b& e& h\cr
    c& f& i\cr}}% each cell expands out to \vbox{\hbox{...
\hbox{\box0\box2\strut}
\nointerlineskip
\vbox{\strut When there is just text inside a \hbox{vbox,}
  it uses the whole hsize and indents\strut}
\nointerlineskip
\vbox{\hbox{\strut except when}\hbox{it consists}\hbox{of consecutive}\hbox{hboxes}}
\tracingoutput2
\bye

this uses box2 rather than box1 which doesn't change anything, but using odd numbered registers for local assignments is naughty:-)

the two alignments have the same total vertical size but the first one had zero depth, adding kern 0pt at the end of the vertical list of the second one, pushes all the size into the height so they match up.

Using a vbox mid-paragraph as in your example rarely does anything useful so I just changed it to an hbox. (The paragraph set in the inner vbox will be set to lines of \hsize wide so they will never really fit in the outer paragraph which is being set to the same measure.)

enter image description here

David Carlisle
  • 757,742
  • That \kern0pt trick is pretty neat. I've no idea how can it do anything. – morbusg Jun 19 '12 at 13:51
  • The depth of a \vbox is zero if the last thing in the list is a non negative skip or kern. – David Carlisle Jun 19 '12 at 14:02
  • I was wondering why is using odd numbered registers naughty? – morbusg Jul 12 '12 at 13:45
  • @morbusg plain and latex reserve registers 0-10 for scratch use but even ones should only be used for local assignments and odd ones for global. If you break this rule (which is in the TeXBook) then either an inner macro will make a global assignment to your scratch register and mess you up, or it won't but you will have local and global assignments to the same register which causes TeX to use unlimited amounts of save stack and give up with a fatal error if you do it too much – David Carlisle Jul 12 '12 at 13:58
  • Cool, thanks for the that! :) I do have the TeXbook, but I must've missed that information. – morbusg Jul 12 '12 at 15:49