5

I have two \vbox typeset in different parts of the document, and I intend to append them as if all their contents were typeset only once in one \vbox. So linespacing remains constant going from one \vboxes contents to another, etc. Is there a clean way to do such an append?

My current technique causes insufficient space between baselines of last line of first \vbox, and first line of second \vbox. Ideally I would want a generic solution that works for appending any two \vboxes, and the result behaves as if all the contents of the final box were typeset once in one \vbox. Corollary: For now assume the line spacing in both boxes was same when they were independently typeset; as a corollary is it possible to have line spacing between last line of first box and first line of second box to be equal to the line spacing used in first box (assuming when the second box was typeset in the document it had a different linespacing).

Check the linespacing problem in this screenshot, and the full code example after it:

vbox append linespacing problem

Code:

% file: vboxappend.tex
% >> lualatex vboxappend.tex
\documentclass[notitlepage,letterpaper]{article}

%NOTE: Uncomment the following line, and run with lualatex if you want to visualize debug markers
%\usepackage{lua-visual-debug}
\usepackage{geometry}
\usepackage[expansion=alltext,shrink=20,stretch=20]{microtype}
\usepackage{fontspec}
\usepackage{blindtext}
\geometry{left=2in,right=2in,top=1in,bottom=1in}
\setmainfont{Verdana}

\begin{document}

% First page without using vboxes
\thispagestyle{empty}
\newcommand{\mytesttext}{\blindtext[1]}
\mytesttext\par
\mytesttext

% Second page using vboxes
\newpage
\thispagestyle{empty}
\newbox\myvboxone
\setbox\myvboxone=\vbox{{\hsize=\textwidth \mytesttext \endgraf}}%
\newbox\myvboxtwo
\setbox\myvboxtwo=\vbox{{\hsize=\textwidth \mytesttext \endgraf}}%
\newbox\myvboxthree
\setbox\myvboxthree=\vbox{{\hsize=\textwidth \unvbox\myvboxone\unvbox\myvboxtwo \endgraf}}%

\box\myvboxthree


\end{document}

3 Answers 3

4

When the vertical material is \unvboxed then the result does not respect baselineskip above and below such material. If we need to add something below such material then \lastbox trick can be used. This removes the last box from \unvboxed material and returns it to the vertical list again, but the \prevdepth is correctly set and next "normal" line will respect baselineskip settings. But you does not have next "normal" line, you have next \ubvboxed material. So new problem is here. This could be solved by \null (i.e. \hbox{}). The code should be:

\newbox\myvboxone
\setbox\myvboxone=\vbox{{\mytesttext \endgraf}}%
\newbox\myvboxtwo
\setbox\myvboxtwo=\vbox{{\null \mytesttext \endgraf}}%
\newbox\myvboxthree
\setbox\myvboxthree=\vbox
   {{\unvbox\myvboxone \lastbox\kern-\prevdepth \unvbox\myvboxtwo}}%

\box\myvboxthree

\bye

Note that the \myvboxone is the same as your. When it is \unvboxed then \lastbox trick is used followed by \kern-\prevdepth. Now, we are exactly at the baseline of the last line. Next \unvboxed material begins with \null which is positioned to the baseline of last line of \myvboxone. Next line has correct \baselineskip because it was calulated when \myvboxtwo was created.

Edit If you want to use \vboxes from \vsplit then the core trick is to set \splittopskip to \baselineskip, to insert first \penalty0 before the column of the text and to do first "dummy" \vsplit in this \penalty0. This inserts the correct lineskip above the first line in the column. Next \vsplits inserts correct lineskip above the rest of the column too. So you are sure that the material from \vplit behaves like as the \null is here (but it is not here:). The connection of splitted parts back together needs only the \lastbox trick now. Example:

\newbox\myvboxone
\newbox\myvboxtwo
\newbox\myvboxthree
\newbox\allcolumn

\setbox\myvboxone=\vbox{{\mytesttext \endgraf}}% begin of the text

\splittopskip=\baselineskip      % << core trick is here + \penaty0
\setbox\allcolumn=\vbox{{\penalty0 \mytesttext \endgraf}}

\setbox0=\vsplit\allcolumn to0pt % this resets \allcolun, now it starts with 
                                 % correct skip above the first line.

\setbox\myvboxtwo=\vsplit\allcolumn to3\baselineskip % three lines from \allcolumn

\setbox\myvboxthree=\vbox  % cat begin of the text with three lines from \allcolumn
   {\unvbox\myvboxone \lastbox\kern-\prevdepth \unvbox\myvboxtwo}%

\bigskip test1:\medskip  \copy\myvboxthree % testing of the result 

\setbox\myvboxtwo=\vsplit\allcolumn to4\baselineskip % next 4 lines from \allcolumn

\setbox\myvboxthree=\vbox % cat previous text with next four lines 
   {\unvbox\myvboxthree \lastbox\kern-\prevdepth \unvbox\myvboxtwo}%

\bigskip test2:\medskip  \box\myvboxthree % testing of the result

\bye
6
  • Thanks, I confirm that this works. Even though I didn't ask this in my original question, here is what I did for some educational testing (to see if this would work with any combination of split followed by append): I split \myvboxtwo and tried to append the bottom split to \myvboxone, and it does not work. Probably because it now lost that \null? Can you think of a way to make any box append work even if that box is a split from another box? Such a solution would not do something (like adding \null or changing prevdepth) special at the time of typesetting either of the vboxes?
    – codepoet
    Commented Apr 12, 2020 at 7:16
  • @reportaman I added new text in my answer concerning to \vboxes from \vsplit.
    – wipet
    Commented Apr 12, 2020 at 8:11
  • Thanks, I tried searching when one would use \penalty0 but could not find a precise answer. Your "TeX in a Nutshell" briefly describes penalty in relation to controlling the TeX line breaking algorithm. You also mention "If no penalty is defined for a given glue, then it is same as a penalty equal to zero". So if default value is zero for penalties, why do you explicitly define one here, and set it to zero? And which glue are you trying to set this penalty on?
    – codepoet
    Commented Apr 13, 2020 at 3:09
  • @reportaman The TeX in a Nutshell does not precise explain all TeX algorithms (as mentioned in the preface). More precise is: The material can be broken in the penalty (with this value of penalty) or in the glue (with zero penalty). The second case is possible only if another penalty or glue does not precede. When the material is broken in an object (a penalty or a glue) then this object is discarded and all immediately following glues or penalties are discarded too. This is more precise description of TeX algorithm. Maybe I will correct TeX in nutshell in this topics.
    – wipet
    Commented Apr 13, 2020 at 4:43
  • 1
    The \penalty0 in our example is used as a place where the material can be broken and the first "dummy \vsplit" breaks the material in this \penalty0. The same effect can be done by inserting \vskip0pt \relax instead \penalty0 here.
    – wipet
    Commented Apr 13, 2020 at 4:47
4

Put \strut at the end of the text in the first vbox, and at the beginning of the text in the second.

That solution works. My initial explanation was wrong because I misread \unvbox as \box: The (incorrect) explanation is that each box is treated like a single huge letter when TeX stacks them in a "vertical list"; TeX does not look inside the boxes to see where the baselines of the interior text are, and makes no attempt to make them regularly spaced.

The real explanation for no baseline-skip is that's a feature of \unvbox. But \strut will still do the job.

I presume you are actually doing something that requires \vbox... Probably \parbox is better in the general case. If you are just trying to prevent page breaks, there are better solutions (including \samepage, which works pretty well, as long as you know it only applies to lines in a paragraph).

There are fancier things possible, to rebox the box contents as \vtop and \vbox to measure the heights and depths of first and last lines, and then set \prevdepth so TeX does the vertical spacing based on contents, even without struts.

7
  • Thanks Donald for the solution, I just did some preliminary testing & it works :) Yes I am doing something that requires \vbox. The one thing I like about it so far is it can be split and/or joined, am not sure if any other boxes (parbox/minipage) can be. Though the one thing I do not like about \vbox (afaik) is that it cannot be nested like minipage.
    – codepoet
    Commented Apr 12, 2020 at 5:20
  • I wonder if I can use \strut liberally, and if it does affect the typesetting of contents. I have used \vphantom in past, and surprisingly even though it is a zero-width entity, it does affect typesetting my moving the contents on the line as if it were real text.
    – codepoet
    Commented Apr 12, 2020 at 5:32
  • 2
    Certainly \strut can affect typesetting by interrupting the ignoring of spaces or by interfering with the hyphenation of words. Starting the text with \strut is no problem because the first word can't hyphenate anyway, and spaces are ignored after command names (after \strut, but not after arguments with \vphantom{}). For the \strut as the last thing in the last paragraph, make sure there is no space before the \strut, or you run the risk of having a blank line at the end of the paragraph (containing only \strut). Commented Apr 12, 2020 at 6:10
  • Cool thanks for confirmation, and explanation. I did try that experiment after posting my last comment, and saw the same results that you mention. Though I did not know why it happens that way, now I know :)
    – codepoet
    Commented Apr 12, 2020 at 6:21
  • Hi there, even though I didn't ask this in my original question, here is what I did for some educational testing (to see if this would work with any combination of split followed by append): I split \myvboxtwo and tried to append the bottom split to \myvboxone, and it does not work; thats probably because the \strut was in the top split, and since we don't know the position of a split upfront we cannot add a \strut upfront at some location. Is there a way to rebox a split \vbox to again add that \strut? That would enable appending \vbox splits to another \vbox.
    – codepoet
    Commented Apr 12, 2020 at 7:30
4

As egreg explained in this answer:

At the start of a \vbox, the parameter \prevdepth is set to -1000pt and, when you do \unvbox, this value inhibits the interline glue.

In your case, \prevdepth is set to -1000pt right after \setbox\myvboxtwo=\vbox{, among others, and this causes the problem you mentioned when \myvboxtwo is \unvboxed. If you save \myvboxone's depth after it has been set and insert it as the \prevdepth value at the beginning of the assignment to \myvboxtwo, the interline glue will be correct when \myvboxtwo is \unvboxed after \myvboxone.

Note: I switched your code to use LaTeX's \newsavebox instead of TeX's \newbox. AFAIK, it does more checks. Also, during your assignment to \myvboxthree, TeX never switches to horizontal mode, therefore the \hsize setting and the \endgraf are useless.

\documentclass{article}
\usepackage[expansion=alltext,shrink=20,stretch=20]{microtype}
\usepackage{fontspec}
\usepackage{blindtext}
\setmainfont{Verdana}

\newcommand{\mytesttext}{\blindtext[1]}
\newdimen\mydim
\pagestyle{empty}

\newsavebox{\myvboxone}
\newsavebox{\myvboxtwo}
\newsavebox{\myvboxthree}

\begin{document}

\setbox\myvboxone=\vbox{{\hsize=\textwidth \mytesttext \endgraf}}
\mydim=\dp\myvboxone

\setbox\myvboxtwo=\vbox{
  \prevdepth=\mydim
  {\hsize=\textwidth \mytesttext \endgraf}
}

\setbox\myvboxthree=\vbox{
  \unvbox\myvboxone
  \unvbox\myvboxtwo
}

\box\myvboxthree

\end{document}

enter image description here

If we add a very deep box or rule in the last line of \myvboxone, like this:

\setbox\myvboxone=\vbox{
  {\hsize=\textwidth \mytesttext
   \vrule width 0.4pt depth 20pt\endgraf}}

its depth is well taken into account by the above solution (note that I don't show again the start of the first box and the end of the second one: they are the same as above):

enter image description here

April 13 edit

Replying to this comment: if I use the following modified definition of \mytesttext:

\newcommand{\mytesttext}{\blindtext[1]ee eeeeeee eee eee ee eee eee ee eee ee
  eee ee eeeeeee eee eee ee eeen}

then zoomed output where the two \vboxes join is as follows:

enter image description here

The interline looks fine to me.

7
  • Thanks, I confirm that this works. Even though I didn't ask this in my original question, here is what I did for some educational testing (to see if this would work with any combination of split followed by append): I split \myvboxtwo and tried to append the bottom split to \myvboxone, and it does not work. Is there a way to rebox a split \vbox to again get that prevdepth? That would enable appending \vbox splits to another \vbox.
    – codepoet
    Commented Apr 12, 2020 at 7:28
  • I have updated the answer.
    – frougon
    Commented Apr 12, 2020 at 8:39
  • 1
    The first vsplitted material can be managed by your concept but the same for next vsplitted material is not possible.
    – wipet
    Commented Apr 12, 2020 at 11:15
  • @wipet Sorry, but this is so far from the original question that I literally have to guess your test case. What I can say is that what I posted in my answer works, and the following also works: \setbox\mysplitbox=\vsplit\myvboxtwo to 50pt \setbox\mysplitbox=\vsplit\myvboxtwo to 30pt \setbox\myvboxthree=\vbox{ \unvbox\myvboxone \unvbox\mysplitbox \unvbox\myvboxtwo } \box\myvboxthree.
    – frougon
    Commented Apr 12, 2020 at 11:26
  • @frougon No, what you posted in the answer does not work for splits, leading is dependent on the contents of the last line of the first \vbox. For instance if the last line only has letters with no descenders like 'e/n/a' then constant leading is not maintained. Append this string to the end of \mytesttext in \myvboxone while you form that \vbox: ee eeeeeee eee eee ee eee eee ee eee ee eee ee eeeeeee eee eee ee eeen. Now open the newly compiled pdf, zoom in, and compare/measure to see the difference in leading. I compare lenghts in pdf by using a rectangular selection tool.
    – codepoet
    Commented Apr 13, 2020 at 0:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .