Spacing difference when using boxes
up vote
16
down vote
favorite
On the left (obtained from the MWE below), I do not use boxes for the title nor the body text and the title and the text appears to be in the correct spot.
However, when I attempt to use newsaveboxs (MWE with defUseBoxes{} uncommented) I obtain the results on the right.


Questions:
- What are this additional skip's being inserted and how do I get both to exhibit identical behavior?
- In my actual use case I can tweak the addition of the
strutsto get correct behavior, but that also seems to need a conditional based on if the body text is more than one line. My understanding is that I should only need astrutat the end of the body text, but that does not seem to work here.
Notes:
- A vertical space defined by
DesiredSkipAboveTitleis added before the title and aDesiredSkipBelowTitleis added after the title. - Need to run at least twice to get the
tikzdrawing in correct position. - The
adjustboxpackage can be used to move the boxes into what I think is the correct position. This can be seen with bothdefUseBoxes{}anddefUseAdjustBox{}uncommented. From this we see that theTitleBoxneeds araise=-1.3ptoption and theBodyBoxrequires araise=-13.3ptwith thealign=t. I would like to know where these amounts come from.
Code:
defUseBoxes{}% Uncomment to use boxes
%defUseAdjustBox{}% Uncomment to use adjustbox (with added fudge factors)
%% --------------------
documentclass{article}
usepackage{tikz}
usepackage{tikzpagenodes}
usepackage[export]{adjustbox}
usepackage[paperwidth=7.0cm,showframe]{geometry}
newcommand*{DesiredSkipAboveTitle}{5pt}
newcommand*{DesiredSkipBelowTitle}{10pt}
newcommand*{NumberOfTitleLines}{2}
newcommand*{Title}{A Title that Takes Up Two Lines}
newsavebox{TitleBox}
newcommand*{SetupTitleBox}[2]{%
setbox#1vbox{%
bfseriescentering%
#2%
%strut%
par%
}%
}
newcommand*{BodyTextLarge}{%
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer id nisl porta, porttitor tellus sed, sollicitudin erat.
Aliquam tellus urna, feugiat et tortor quis, aliquam mattis orci.%
}%
newcommand*{BodyTextSmall}{%
Small Text.%
}%
newcommand{SelectedBodyText}{BodyTextLarge}
%newcommand{SelectedBodyText}{BodyTextSmall}
newsavebox{BodyBox}
newcommand{SetupBodyBox}[2]{%
setbox#1vbox{%
noindent#2%
%strut%
}%
}%
newlength{AdditionalSkip}
newcommand*{ShowTextGuideLines}[1]{%
begin{tikzpicture}[remember picture, overlay]
coordinate (X) at (current page text area.north west);
draw [draw=red, fill=yellow, fill opacity=0.2]
(X) circle (1pt);%% DEBUGGING: Ensure (X) is the correct spot.
setlength{AdditionalSkip}{DesiredSkipAboveTitle}%
foreach X in {1, ..., #1} {%
ifnumX>NumberOfTitleLines
%% After title: need to adjust AdditionalSkip for space after title
setlength{AdditionalSkip}{%
dimexprDesiredSkipAboveTitle+DesiredSkipBelowTitlerelax%
}%
fi
draw [thin, red] ([yshift=-Xbaselineskip-AdditionalSkip]X) -- ++ (hsize,0);
}%
tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
ifdefinedUseBoxes
node [Node Style, fill=yellow] at ([xshift=0.5hsize]X) {using boxes};
else
node [Node Style, fill=green] at ([xshift=0.5hsize]X) {Not using boxes};
fi
end{tikzpicture}%
}%
begin{document}
%% ---------------------------------------------------------- Set up the title and body
SetupTitleBox{TitleBox}{Title}%
SetupBodyBox{BodyBox}{SelectedBodyText}%
%% ---------------------------------------------------------- Title
hbox{}kern-topskip%% See https://tex.stackexchange.com/questions/7676/why-does-vspace0pt-add-vertical-space#comment12433_7681
vspace*{DesiredSkipAboveTitle}%
%%
ifdefinedUseBoxes
ifdefinedUseAdjustBox% Following tweaks seem to be close
noindentadjustbox{valign=t,raise=-1.3pt}{usebox{TitleBox}}%
vspace*{DesiredSkipBelowTitle}%
parnoindentadjustbox{valign=t,raise=-13.3pt}{usebox{BodyBox}}%
else
noindentusebox{TitleBox}%
vspace*{DesiredSkipBelowTitle}%
parnoindentusebox{BodyBox}%
fi
else
noindent{bfseriescenteringTitlepar}%
vspace*{DesiredSkipBelowTitle}%
parnoindentSelectedBodyText%
fi
noindentShowTextGuideLines{7}%
end{document}
spacing vertical-alignment boxes dimensions
This question has an open bounty worth +400
reputation from Peter Grill ending in 6 days.
One or more of the answers is exemplary and worthy of an additional bounty.
The detailed explanation was very helpful in understanding the issues.
add a comment |
up vote
16
down vote
favorite
On the left (obtained from the MWE below), I do not use boxes for the title nor the body text and the title and the text appears to be in the correct spot.
However, when I attempt to use newsaveboxs (MWE with defUseBoxes{} uncommented) I obtain the results on the right.


Questions:
- What are this additional skip's being inserted and how do I get both to exhibit identical behavior?
- In my actual use case I can tweak the addition of the
strutsto get correct behavior, but that also seems to need a conditional based on if the body text is more than one line. My understanding is that I should only need astrutat the end of the body text, but that does not seem to work here.
Notes:
- A vertical space defined by
DesiredSkipAboveTitleis added before the title and aDesiredSkipBelowTitleis added after the title. - Need to run at least twice to get the
tikzdrawing in correct position. - The
adjustboxpackage can be used to move the boxes into what I think is the correct position. This can be seen with bothdefUseBoxes{}anddefUseAdjustBox{}uncommented. From this we see that theTitleBoxneeds araise=-1.3ptoption and theBodyBoxrequires araise=-13.3ptwith thealign=t. I would like to know where these amounts come from.
Code:
defUseBoxes{}% Uncomment to use boxes
%defUseAdjustBox{}% Uncomment to use adjustbox (with added fudge factors)
%% --------------------
documentclass{article}
usepackage{tikz}
usepackage{tikzpagenodes}
usepackage[export]{adjustbox}
usepackage[paperwidth=7.0cm,showframe]{geometry}
newcommand*{DesiredSkipAboveTitle}{5pt}
newcommand*{DesiredSkipBelowTitle}{10pt}
newcommand*{NumberOfTitleLines}{2}
newcommand*{Title}{A Title that Takes Up Two Lines}
newsavebox{TitleBox}
newcommand*{SetupTitleBox}[2]{%
setbox#1vbox{%
bfseriescentering%
#2%
%strut%
par%
}%
}
newcommand*{BodyTextLarge}{%
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer id nisl porta, porttitor tellus sed, sollicitudin erat.
Aliquam tellus urna, feugiat et tortor quis, aliquam mattis orci.%
}%
newcommand*{BodyTextSmall}{%
Small Text.%
}%
newcommand{SelectedBodyText}{BodyTextLarge}
%newcommand{SelectedBodyText}{BodyTextSmall}
newsavebox{BodyBox}
newcommand{SetupBodyBox}[2]{%
setbox#1vbox{%
noindent#2%
%strut%
}%
}%
newlength{AdditionalSkip}
newcommand*{ShowTextGuideLines}[1]{%
begin{tikzpicture}[remember picture, overlay]
coordinate (X) at (current page text area.north west);
draw [draw=red, fill=yellow, fill opacity=0.2]
(X) circle (1pt);%% DEBUGGING: Ensure (X) is the correct spot.
setlength{AdditionalSkip}{DesiredSkipAboveTitle}%
foreach X in {1, ..., #1} {%
ifnumX>NumberOfTitleLines
%% After title: need to adjust AdditionalSkip for space after title
setlength{AdditionalSkip}{%
dimexprDesiredSkipAboveTitle+DesiredSkipBelowTitlerelax%
}%
fi
draw [thin, red] ([yshift=-Xbaselineskip-AdditionalSkip]X) -- ++ (hsize,0);
}%
tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
ifdefinedUseBoxes
node [Node Style, fill=yellow] at ([xshift=0.5hsize]X) {using boxes};
else
node [Node Style, fill=green] at ([xshift=0.5hsize]X) {Not using boxes};
fi
end{tikzpicture}%
}%
begin{document}
%% ---------------------------------------------------------- Set up the title and body
SetupTitleBox{TitleBox}{Title}%
SetupBodyBox{BodyBox}{SelectedBodyText}%
%% ---------------------------------------------------------- Title
hbox{}kern-topskip%% See https://tex.stackexchange.com/questions/7676/why-does-vspace0pt-add-vertical-space#comment12433_7681
vspace*{DesiredSkipAboveTitle}%
%%
ifdefinedUseBoxes
ifdefinedUseAdjustBox% Following tweaks seem to be close
noindentadjustbox{valign=t,raise=-1.3pt}{usebox{TitleBox}}%
vspace*{DesiredSkipBelowTitle}%
parnoindentadjustbox{valign=t,raise=-13.3pt}{usebox{BodyBox}}%
else
noindentusebox{TitleBox}%
vspace*{DesiredSkipBelowTitle}%
parnoindentusebox{BodyBox}%
fi
else
noindent{bfseriescenteringTitlepar}%
vspace*{DesiredSkipBelowTitle}%
parnoindentSelectedBodyText%
fi
noindentShowTextGuideLines{7}%
end{document}
spacing vertical-alignment boxes dimensions
This question has an open bounty worth +400
reputation from Peter Grill ending in 6 days.
One or more of the answers is exemplary and worthy of an additional bounty.
The detailed explanation was very helpful in understanding the issues.
2
you used vbox rather than vtop (not that either of them are really latex)
– David Carlisle
Dec 3 at 20:41
@DavidCarlisle: Usingvtopinstead ofvboxseems to resolve the issue with theTitleBox, but not theBodyBox?
– Peter Grill
Dec 3 at 20:57
2
I didn't actually run the code, I'll look in a bit:-) but getting spacing around boxes right is tricky you need to control the first and last baseline but boxes only have one reference point, hence latex3 coffins, boxes with handles.
– David Carlisle
Dec 3 at 20:58
add a comment |
up vote
16
down vote
favorite
up vote
16
down vote
favorite
On the left (obtained from the MWE below), I do not use boxes for the title nor the body text and the title and the text appears to be in the correct spot.
However, when I attempt to use newsaveboxs (MWE with defUseBoxes{} uncommented) I obtain the results on the right.


Questions:
- What are this additional skip's being inserted and how do I get both to exhibit identical behavior?
- In my actual use case I can tweak the addition of the
strutsto get correct behavior, but that also seems to need a conditional based on if the body text is more than one line. My understanding is that I should only need astrutat the end of the body text, but that does not seem to work here.
Notes:
- A vertical space defined by
DesiredSkipAboveTitleis added before the title and aDesiredSkipBelowTitleis added after the title. - Need to run at least twice to get the
tikzdrawing in correct position. - The
adjustboxpackage can be used to move the boxes into what I think is the correct position. This can be seen with bothdefUseBoxes{}anddefUseAdjustBox{}uncommented. From this we see that theTitleBoxneeds araise=-1.3ptoption and theBodyBoxrequires araise=-13.3ptwith thealign=t. I would like to know where these amounts come from.
Code:
defUseBoxes{}% Uncomment to use boxes
%defUseAdjustBox{}% Uncomment to use adjustbox (with added fudge factors)
%% --------------------
documentclass{article}
usepackage{tikz}
usepackage{tikzpagenodes}
usepackage[export]{adjustbox}
usepackage[paperwidth=7.0cm,showframe]{geometry}
newcommand*{DesiredSkipAboveTitle}{5pt}
newcommand*{DesiredSkipBelowTitle}{10pt}
newcommand*{NumberOfTitleLines}{2}
newcommand*{Title}{A Title that Takes Up Two Lines}
newsavebox{TitleBox}
newcommand*{SetupTitleBox}[2]{%
setbox#1vbox{%
bfseriescentering%
#2%
%strut%
par%
}%
}
newcommand*{BodyTextLarge}{%
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer id nisl porta, porttitor tellus sed, sollicitudin erat.
Aliquam tellus urna, feugiat et tortor quis, aliquam mattis orci.%
}%
newcommand*{BodyTextSmall}{%
Small Text.%
}%
newcommand{SelectedBodyText}{BodyTextLarge}
%newcommand{SelectedBodyText}{BodyTextSmall}
newsavebox{BodyBox}
newcommand{SetupBodyBox}[2]{%
setbox#1vbox{%
noindent#2%
%strut%
}%
}%
newlength{AdditionalSkip}
newcommand*{ShowTextGuideLines}[1]{%
begin{tikzpicture}[remember picture, overlay]
coordinate (X) at (current page text area.north west);
draw [draw=red, fill=yellow, fill opacity=0.2]
(X) circle (1pt);%% DEBUGGING: Ensure (X) is the correct spot.
setlength{AdditionalSkip}{DesiredSkipAboveTitle}%
foreach X in {1, ..., #1} {%
ifnumX>NumberOfTitleLines
%% After title: need to adjust AdditionalSkip for space after title
setlength{AdditionalSkip}{%
dimexprDesiredSkipAboveTitle+DesiredSkipBelowTitlerelax%
}%
fi
draw [thin, red] ([yshift=-Xbaselineskip-AdditionalSkip]X) -- ++ (hsize,0);
}%
tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
ifdefinedUseBoxes
node [Node Style, fill=yellow] at ([xshift=0.5hsize]X) {using boxes};
else
node [Node Style, fill=green] at ([xshift=0.5hsize]X) {Not using boxes};
fi
end{tikzpicture}%
}%
begin{document}
%% ---------------------------------------------------------- Set up the title and body
SetupTitleBox{TitleBox}{Title}%
SetupBodyBox{BodyBox}{SelectedBodyText}%
%% ---------------------------------------------------------- Title
hbox{}kern-topskip%% See https://tex.stackexchange.com/questions/7676/why-does-vspace0pt-add-vertical-space#comment12433_7681
vspace*{DesiredSkipAboveTitle}%
%%
ifdefinedUseBoxes
ifdefinedUseAdjustBox% Following tweaks seem to be close
noindentadjustbox{valign=t,raise=-1.3pt}{usebox{TitleBox}}%
vspace*{DesiredSkipBelowTitle}%
parnoindentadjustbox{valign=t,raise=-13.3pt}{usebox{BodyBox}}%
else
noindentusebox{TitleBox}%
vspace*{DesiredSkipBelowTitle}%
parnoindentusebox{BodyBox}%
fi
else
noindent{bfseriescenteringTitlepar}%
vspace*{DesiredSkipBelowTitle}%
parnoindentSelectedBodyText%
fi
noindentShowTextGuideLines{7}%
end{document}
spacing vertical-alignment boxes dimensions
On the left (obtained from the MWE below), I do not use boxes for the title nor the body text and the title and the text appears to be in the correct spot.
However, when I attempt to use newsaveboxs (MWE with defUseBoxes{} uncommented) I obtain the results on the right.


Questions:
- What are this additional skip's being inserted and how do I get both to exhibit identical behavior?
- In my actual use case I can tweak the addition of the
strutsto get correct behavior, but that also seems to need a conditional based on if the body text is more than one line. My understanding is that I should only need astrutat the end of the body text, but that does not seem to work here.
Notes:
- A vertical space defined by
DesiredSkipAboveTitleis added before the title and aDesiredSkipBelowTitleis added after the title. - Need to run at least twice to get the
tikzdrawing in correct position. - The
adjustboxpackage can be used to move the boxes into what I think is the correct position. This can be seen with bothdefUseBoxes{}anddefUseAdjustBox{}uncommented. From this we see that theTitleBoxneeds araise=-1.3ptoption and theBodyBoxrequires araise=-13.3ptwith thealign=t. I would like to know where these amounts come from.
Code:
defUseBoxes{}% Uncomment to use boxes
%defUseAdjustBox{}% Uncomment to use adjustbox (with added fudge factors)
%% --------------------
documentclass{article}
usepackage{tikz}
usepackage{tikzpagenodes}
usepackage[export]{adjustbox}
usepackage[paperwidth=7.0cm,showframe]{geometry}
newcommand*{DesiredSkipAboveTitle}{5pt}
newcommand*{DesiredSkipBelowTitle}{10pt}
newcommand*{NumberOfTitleLines}{2}
newcommand*{Title}{A Title that Takes Up Two Lines}
newsavebox{TitleBox}
newcommand*{SetupTitleBox}[2]{%
setbox#1vbox{%
bfseriescentering%
#2%
%strut%
par%
}%
}
newcommand*{BodyTextLarge}{%
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer id nisl porta, porttitor tellus sed, sollicitudin erat.
Aliquam tellus urna, feugiat et tortor quis, aliquam mattis orci.%
}%
newcommand*{BodyTextSmall}{%
Small Text.%
}%
newcommand{SelectedBodyText}{BodyTextLarge}
%newcommand{SelectedBodyText}{BodyTextSmall}
newsavebox{BodyBox}
newcommand{SetupBodyBox}[2]{%
setbox#1vbox{%
noindent#2%
%strut%
}%
}%
newlength{AdditionalSkip}
newcommand*{ShowTextGuideLines}[1]{%
begin{tikzpicture}[remember picture, overlay]
coordinate (X) at (current page text area.north west);
draw [draw=red, fill=yellow, fill opacity=0.2]
(X) circle (1pt);%% DEBUGGING: Ensure (X) is the correct spot.
setlength{AdditionalSkip}{DesiredSkipAboveTitle}%
foreach X in {1, ..., #1} {%
ifnumX>NumberOfTitleLines
%% After title: need to adjust AdditionalSkip for space after title
setlength{AdditionalSkip}{%
dimexprDesiredSkipAboveTitle+DesiredSkipBelowTitlerelax%
}%
fi
draw [thin, red] ([yshift=-Xbaselineskip-AdditionalSkip]X) -- ++ (hsize,0);
}%
tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
ifdefinedUseBoxes
node [Node Style, fill=yellow] at ([xshift=0.5hsize]X) {using boxes};
else
node [Node Style, fill=green] at ([xshift=0.5hsize]X) {Not using boxes};
fi
end{tikzpicture}%
}%
begin{document}
%% ---------------------------------------------------------- Set up the title and body
SetupTitleBox{TitleBox}{Title}%
SetupBodyBox{BodyBox}{SelectedBodyText}%
%% ---------------------------------------------------------- Title
hbox{}kern-topskip%% See https://tex.stackexchange.com/questions/7676/why-does-vspace0pt-add-vertical-space#comment12433_7681
vspace*{DesiredSkipAboveTitle}%
%%
ifdefinedUseBoxes
ifdefinedUseAdjustBox% Following tweaks seem to be close
noindentadjustbox{valign=t,raise=-1.3pt}{usebox{TitleBox}}%
vspace*{DesiredSkipBelowTitle}%
parnoindentadjustbox{valign=t,raise=-13.3pt}{usebox{BodyBox}}%
else
noindentusebox{TitleBox}%
vspace*{DesiredSkipBelowTitle}%
parnoindentusebox{BodyBox}%
fi
else
noindent{bfseriescenteringTitlepar}%
vspace*{DesiredSkipBelowTitle}%
parnoindentSelectedBodyText%
fi
noindentShowTextGuideLines{7}%
end{document}
spacing vertical-alignment boxes dimensions
spacing vertical-alignment boxes dimensions
edited Dec 7 at 0:44
asked Dec 3 at 20:05
Peter Grill
163k24432744
163k24432744
This question has an open bounty worth +400
reputation from Peter Grill ending in 6 days.
One or more of the answers is exemplary and worthy of an additional bounty.
The detailed explanation was very helpful in understanding the issues.
This question has an open bounty worth +400
reputation from Peter Grill ending in 6 days.
One or more of the answers is exemplary and worthy of an additional bounty.
The detailed explanation was very helpful in understanding the issues.
2
you used vbox rather than vtop (not that either of them are really latex)
– David Carlisle
Dec 3 at 20:41
@DavidCarlisle: Usingvtopinstead ofvboxseems to resolve the issue with theTitleBox, but not theBodyBox?
– Peter Grill
Dec 3 at 20:57
2
I didn't actually run the code, I'll look in a bit:-) but getting spacing around boxes right is tricky you need to control the first and last baseline but boxes only have one reference point, hence latex3 coffins, boxes with handles.
– David Carlisle
Dec 3 at 20:58
add a comment |
2
you used vbox rather than vtop (not that either of them are really latex)
– David Carlisle
Dec 3 at 20:41
@DavidCarlisle: Usingvtopinstead ofvboxseems to resolve the issue with theTitleBox, but not theBodyBox?
– Peter Grill
Dec 3 at 20:57
2
I didn't actually run the code, I'll look in a bit:-) but getting spacing around boxes right is tricky you need to control the first and last baseline but boxes only have one reference point, hence latex3 coffins, boxes with handles.
– David Carlisle
Dec 3 at 20:58
2
2
you used vbox rather than vtop (not that either of them are really latex)
– David Carlisle
Dec 3 at 20:41
you used vbox rather than vtop (not that either of them are really latex)
– David Carlisle
Dec 3 at 20:41
@DavidCarlisle: Using
vtop instead of vbox seems to resolve the issue with the TitleBox, but not the BodyBox?– Peter Grill
Dec 3 at 20:57
@DavidCarlisle: Using
vtop instead of vbox seems to resolve the issue with the TitleBox, but not the BodyBox?– Peter Grill
Dec 3 at 20:57
2
2
I didn't actually run the code, I'll look in a bit:-) but getting spacing around boxes right is tricky you need to control the first and last baseline but boxes only have one reference point, hence latex3 coffins, boxes with handles.
– David Carlisle
Dec 3 at 20:58
I didn't actually run the code, I'll look in a bit:-) but getting spacing around boxes right is tricky you need to control the first and last baseline but boxes only have one reference point, hence latex3 coffins, boxes with handles.
– David Carlisle
Dec 3 at 20:58
add a comment |
1 Answer
1
active
oldest
votes
up vote
13
down vote
accepted
I think it would probably be useful to understand how TeX normally inserts vertical space between lines/boxes.
This will make it clear why this spacing is different when you wrap your paragraphs in a vbox, as in your MWE.
A method to achieve identical spacing with and without boxes can be found at the bottom this answer.
About baselineskip and lineskip
TeX normally tries to insert an appropriate amount of space between lines so that subsequent baselines are separated by baselineskip.
If it fails to do so without causing the distance between the boxes containing these lines to fall below lineskiplimit it'll insert lineskip instead.
Since lineskiplimit=0pt by default, this happens precisely when the boxes would otherwise overlap.
Because baselineskip=12pt, and lineskiplimit=0pt by default for a 10pt document, this means that if the distance between consecutive baselines is 12pt if the sum of the depth of the top line and the height of the bottom line is less than 12pt. Otherwise, the (boxes containing these) lines will be separated by lineskip (which is 1pt by default).
Here's an illustration:
parskip=0pt parindent=0pt
An ordinary line of text
par
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
par
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

Note that if you manually add additional skips, these are just added to whatever amount of space would otherwise be inserted:
parskip=0pt parindent=0pt
An ordinary line of text
parvspace{2mm}
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
parvspace{2mm}
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

The same thing, but with vertical boxes
It is no different for your vboxes. A vbox containing multiple lines has just a single baseline, which coincides with the baseline of the bottom line of its contents (top line for a vtop).
If TeX can't separate these baselines by exactly baselineskip without the boxes overlapping, it will completely ignore baselineskip, put a vertical space of lineskip between these boxes and call it a day.
This is the result: (green lines indicate the baselines)
parskip=0pt parindent=0pt
vbox{The first vbox containing\two lines}
par
vbox{The first vbox containing\two lines}

So the distance between the baseline of the second line of the first box and the baseline of the first line of the second box is equal to the depth of the former line plus the height of the latter line plus lineskip.
In this case I think that's 0pt + 6.94444pt + 1pt = 7.94444pt (the second number is how tall the letters h, d and b are).
Adding a strut or some character with descenders (gjpqy) to the first vbox would increase this distance by increasing the depth of the last line of this box.
The same thing happens between consecutive vtops:
parskip=0pt parindent=0pt
vtop{The first vtop containing\two lines}
par
vtop{The first vtop containing\two lines}

The only situation that likely matches your expected/desired outcome is the following:
parskip=0pt parindent=0pt
vbox{A vbox containing\two lines}
par
vtop{A vtop containing\two lines}

This happens because the baselines are close enough together that they can be separated by exactly baselineskip without the boxes overlapping.
The space above this vbox and below this vtop is again going to be "wrong", however.
The top of the page
Something similar happens at the top of the page.
TeX tries to insert a (positive) skip that wil separate the baseline of the first box on a page from the top of the text area by topskip. If it can't, it'll just place the box directly at the top.
The default value of topskip is equal to the font size (so 10pt for a 10pt document).
This is why it matters whether you put a vtop or a vbox at the top of your page.
documentclass{article}
usepackage[showframe]{geometry}
begin{document}
parskip=0pt parindent=0pt
vtop{A vtop containing\two lines}
clearpage
vbox{A vbox containing\two lines}
end{document}


In your MWE topskip isn't really relevant because you've inserted hbox{}kern-topskip. The hbox creates an empty box that will be separated from the top of the text area by the full topskip, and the subsequent kern undoes this skip.
It is thus as if there is an invisible paragraph at the top of your document that ends where the text area begins, so TeX tries to separate the first box (after this) from the top of the text area by baselineskip and insert lineskip if it can't, exactly as described above.
A possible solution
To get the correct spacing above a vertical box you should use vtop instead of vbox for the reasons outlined above.
To get the right amount of spacing below a vtop you can set its depth to zero and then insert a vertical skip just below the box equal to <n>baselineskip, where <n> is the number of lines in the box beyond the first. This skip can be calculated by taking the depth of the box and rounding down to a multiple baselineskip. You may want to set lineskiplimit=-maxdimen (inside the box, or globally) to ensure that all baselines inside the box are separated by exactly baselineskip.
The macro copyboxwithappropriatespace, which I define below (and which could use a better name), does almost precisely these two things. Instead of setting the depth to zero it sets it to the remainder of the division of the original depth by baselineskip (as suggested by jfbu in the comments).
This matters if the last line of your box is particularly deep or the first line of the next box is particularly high.
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
This command replaces copy (or usebox).
(If you want, you could define useboxwithappropriatespace by copying this definition, removing dp#1=@tempdimarelax and replacing copy by usebox in this definition.)
documentclass{article}
usepackage{showframe} %% <- show boundary of text area
usepackage{tikz} %% <- To draw the red brackets
usepackage{tikzpagenodes} %% <- to place them correctly
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
parindent=0pt %% <- saves me from having to type noindent
parskip=0pt %% <- default value is "0pt plus 1pt"
begin{document}
newsavebox{TitleBox}
newsavebox{BodyBox}
setboxTitleBox=vtop{textbf{A title that takes\up more than\two lines}}
setboxBodyBox=vtop{Lorem\ipsum}
copyboxwithappropriatespaceTitleBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
begin{tikzpicture}[remember picture, overlay,]
draw[red] (current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{topskip}}} +(-2pt,-topskip) -- +(0,-topskip);
foreach X in {0, ..., 7} {
draw[red] ([yshift=-topskip-Xbaselineskip]current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{baselineskip}}} +(-2pt,-baselineskip) -- +(0,-baselineskip);
}
end{tikzpicture}
clearpage
textbf{A title that takes\up more than\two lines}
Lorem\ipsum par Lorem\ipsum par Lorem\ipsum
end{document}
This produces two pages that look identical (apart from the fact that only the first has the red brackets) and which were constructed differently: one using vertical boxes and one using ordinary paragraphs.

Note that I've set parskip=0pt because the default value of parskip is not equal to 0pt but to 0pt plus 1pt.
This means that the amount of space between paragraphs can be stretched to up to 1pt to improve the page layout, which I'm guessing you don't want to happen.
In your MWE
The spacing in your MWE will be correct if you replace vbox by top and replace usebox by copyboxwithappropriatespace (and add the above definition to your preamble, of course):
defUseBoxes{}% Uncomment to use boxes
%% --------------------
documentclass{article}
usepackage{tikz}
usepackage{tikzpagenodes}
usepackage[paperwidth=7.0cm,showframe]{geometry}
newcommand*{DesiredSkipAboveTitle}{5pt}
newcommand*{DesiredSkipBelowTitle}{10pt}
newcommand*{NumberOfTitleLines}{2}
newcommand*{Title}{A Title that Takes Up Two Lines}
newsavebox{TitleBox}
newcommand*{SetupTitleBox}[2]{%
setbox#1vtop{%
bfseriescentering%
#2%
%strut%
par%
}%
}
newcommand*{BodyTextLarge}{%
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer id nisl porta, porttitor tellus sed, sollicitudin erat.
Aliquam tellus urna, feugiat et tortor quis, aliquam mattis orci.%
}%
newcommand*{BodyTextSmall}{%
Small Text.%
}%
newcommand{SelectedBodyText}{BodyTextLarge}
%newcommand{SelectedBodyText}{BodyTextSmall}
newsavebox{BodyBox}
newcommand{SetupBodyBox}[2]{%
setbox#1vtop{%
noindent#2%
%strut%
}%
}%
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
newlength{AdditionalSkip}
newcommand*{ShowTextGuideLines}[1]{%
begin{tikzpicture}[remember picture, overlay]
coordinate (X) at (current page text area.north west);
draw [draw=red, fill=yellow, fill opacity=0.2]
(X) circle (1pt);%% DEBUGGING: Ensure (X) is the correct spot.
setlength{AdditionalSkip}{DesiredSkipAboveTitle}%
foreach X in {1, ..., #1} {%
ifnumX>NumberOfTitleLines
%% After title: need to adjust AdditionalSkip for space after title
setlength{AdditionalSkip}{%
dimexprDesiredSkipAboveTitle+DesiredSkipBelowTitlerelax%
}%
fi
draw [thin, red] ([yshift=-Xbaselineskip-AdditionalSkip]X) -- ++ (hsize,0);
}%
tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
ifdefinedUseBoxes
node [Node Style, fill=yellow] at ([xshift=0.5hsize]X) {using boxes};
else
node [Node Style, fill=green] at ([xshift=0.5hsize]X) {Not using boxes};
fi
end{tikzpicture}%
}%
begin{document}
%% ---------------------------------------------------------- Set up the title and body
SetupTitleBox{TitleBox}{Title}%
SetupBodyBox{BodyBox}{SelectedBodyText}%
%% ---------------------------------------------------------- Title
hbox{}kern-topskip%% See https://tex.stackexchange.com/questions/7676/why-does-vspace0pt-add-vertical-space#comment12433_7681
vspace*{DesiredSkipAboveTitle}%
%%
ifdefinedUseBoxes
noindentcopyboxwithappropriatespace{TitleBox}%
vspace*{DesiredSkipBelowTitle}%
parnoindentcopyboxwithappropriatespace{BodyBox}%
else
noindent{bfseriescenteringTitlepar}%
vspace*{DesiredSkipBelowTitle}%
parnoindentSelectedBodyText%
fi
noindentShowTextGuideLines{7}%
end{document}

1
Thanks for the detailed explanation.
– Peter Grill
yesterday
You're welcome, and thank you for your generosity. The second bounty wasn't necessary.
– Circumscribe
yesterday
+1 but I believe there is an issue incopyboxwithappropriatespace: it should not "insert multiple of baselineskip closest to@tempdima" but "insert largest multiple of baselineskip less than@tempdima". And not set thedpof the inserted box to zero, but to the remainder of@tempdima(i.e. the actual depth of thevtopbox) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdimathendividecount@ by baselineskip. Thencount@has this largest integer and it timesbaselineskipis to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespaceisn't perfect and was really only intended for this specific use case. If your boxes contain just normal lines of text, then it doesn't really matter whether you round down or to the nearest integer and this missed depth isn't very relevant, but in general it would be better to do precisely what you say. (It is annoying thatdimexprdoesn't just round down btw…)
– Circumscribe
19 hours ago
I only just now thought of a better way to accomplish the same thing: you can create both avboxand avtopand then insert avtopwith the depth of thevboxand avspace*equal to the difference between their depths! This would also work if the distance between the baselines inside the box varies, (My answer is getting a little long though…)
– Circumscribe
19 hours ago
|
show 6 more comments
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
13
down vote
accepted
I think it would probably be useful to understand how TeX normally inserts vertical space between lines/boxes.
This will make it clear why this spacing is different when you wrap your paragraphs in a vbox, as in your MWE.
A method to achieve identical spacing with and without boxes can be found at the bottom this answer.
About baselineskip and lineskip
TeX normally tries to insert an appropriate amount of space between lines so that subsequent baselines are separated by baselineskip.
If it fails to do so without causing the distance between the boxes containing these lines to fall below lineskiplimit it'll insert lineskip instead.
Since lineskiplimit=0pt by default, this happens precisely when the boxes would otherwise overlap.
Because baselineskip=12pt, and lineskiplimit=0pt by default for a 10pt document, this means that if the distance between consecutive baselines is 12pt if the sum of the depth of the top line and the height of the bottom line is less than 12pt. Otherwise, the (boxes containing these) lines will be separated by lineskip (which is 1pt by default).
Here's an illustration:
parskip=0pt parindent=0pt
An ordinary line of text
par
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
par
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

Note that if you manually add additional skips, these are just added to whatever amount of space would otherwise be inserted:
parskip=0pt parindent=0pt
An ordinary line of text
parvspace{2mm}
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
parvspace{2mm}
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

The same thing, but with vertical boxes
It is no different for your vboxes. A vbox containing multiple lines has just a single baseline, which coincides with the baseline of the bottom line of its contents (top line for a vtop).
If TeX can't separate these baselines by exactly baselineskip without the boxes overlapping, it will completely ignore baselineskip, put a vertical space of lineskip between these boxes and call it a day.
This is the result: (green lines indicate the baselines)
parskip=0pt parindent=0pt
vbox{The first vbox containing\two lines}
par
vbox{The first vbox containing\two lines}

So the distance between the baseline of the second line of the first box and the baseline of the first line of the second box is equal to the depth of the former line plus the height of the latter line plus lineskip.
In this case I think that's 0pt + 6.94444pt + 1pt = 7.94444pt (the second number is how tall the letters h, d and b are).
Adding a strut or some character with descenders (gjpqy) to the first vbox would increase this distance by increasing the depth of the last line of this box.
The same thing happens between consecutive vtops:
parskip=0pt parindent=0pt
vtop{The first vtop containing\two lines}
par
vtop{The first vtop containing\two lines}

The only situation that likely matches your expected/desired outcome is the following:
parskip=0pt parindent=0pt
vbox{A vbox containing\two lines}
par
vtop{A vtop containing\two lines}

This happens because the baselines are close enough together that they can be separated by exactly baselineskip without the boxes overlapping.
The space above this vbox and below this vtop is again going to be "wrong", however.
The top of the page
Something similar happens at the top of the page.
TeX tries to insert a (positive) skip that wil separate the baseline of the first box on a page from the top of the text area by topskip. If it can't, it'll just place the box directly at the top.
The default value of topskip is equal to the font size (so 10pt for a 10pt document).
This is why it matters whether you put a vtop or a vbox at the top of your page.
documentclass{article}
usepackage[showframe]{geometry}
begin{document}
parskip=0pt parindent=0pt
vtop{A vtop containing\two lines}
clearpage
vbox{A vbox containing\two lines}
end{document}


In your MWE topskip isn't really relevant because you've inserted hbox{}kern-topskip. The hbox creates an empty box that will be separated from the top of the text area by the full topskip, and the subsequent kern undoes this skip.
It is thus as if there is an invisible paragraph at the top of your document that ends where the text area begins, so TeX tries to separate the first box (after this) from the top of the text area by baselineskip and insert lineskip if it can't, exactly as described above.
A possible solution
To get the correct spacing above a vertical box you should use vtop instead of vbox for the reasons outlined above.
To get the right amount of spacing below a vtop you can set its depth to zero and then insert a vertical skip just below the box equal to <n>baselineskip, where <n> is the number of lines in the box beyond the first. This skip can be calculated by taking the depth of the box and rounding down to a multiple baselineskip. You may want to set lineskiplimit=-maxdimen (inside the box, or globally) to ensure that all baselines inside the box are separated by exactly baselineskip.
The macro copyboxwithappropriatespace, which I define below (and which could use a better name), does almost precisely these two things. Instead of setting the depth to zero it sets it to the remainder of the division of the original depth by baselineskip (as suggested by jfbu in the comments).
This matters if the last line of your box is particularly deep or the first line of the next box is particularly high.
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
This command replaces copy (or usebox).
(If you want, you could define useboxwithappropriatespace by copying this definition, removing dp#1=@tempdimarelax and replacing copy by usebox in this definition.)
documentclass{article}
usepackage{showframe} %% <- show boundary of text area
usepackage{tikz} %% <- To draw the red brackets
usepackage{tikzpagenodes} %% <- to place them correctly
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
parindent=0pt %% <- saves me from having to type noindent
parskip=0pt %% <- default value is "0pt plus 1pt"
begin{document}
newsavebox{TitleBox}
newsavebox{BodyBox}
setboxTitleBox=vtop{textbf{A title that takes\up more than\two lines}}
setboxBodyBox=vtop{Lorem\ipsum}
copyboxwithappropriatespaceTitleBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
begin{tikzpicture}[remember picture, overlay,]
draw[red] (current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{topskip}}} +(-2pt,-topskip) -- +(0,-topskip);
foreach X in {0, ..., 7} {
draw[red] ([yshift=-topskip-Xbaselineskip]current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{baselineskip}}} +(-2pt,-baselineskip) -- +(0,-baselineskip);
}
end{tikzpicture}
clearpage
textbf{A title that takes\up more than\two lines}
Lorem\ipsum par Lorem\ipsum par Lorem\ipsum
end{document}
This produces two pages that look identical (apart from the fact that only the first has the red brackets) and which were constructed differently: one using vertical boxes and one using ordinary paragraphs.

Note that I've set parskip=0pt because the default value of parskip is not equal to 0pt but to 0pt plus 1pt.
This means that the amount of space between paragraphs can be stretched to up to 1pt to improve the page layout, which I'm guessing you don't want to happen.
In your MWE
The spacing in your MWE will be correct if you replace vbox by top and replace usebox by copyboxwithappropriatespace (and add the above definition to your preamble, of course):
defUseBoxes{}% Uncomment to use boxes
%% --------------------
documentclass{article}
usepackage{tikz}
usepackage{tikzpagenodes}
usepackage[paperwidth=7.0cm,showframe]{geometry}
newcommand*{DesiredSkipAboveTitle}{5pt}
newcommand*{DesiredSkipBelowTitle}{10pt}
newcommand*{NumberOfTitleLines}{2}
newcommand*{Title}{A Title that Takes Up Two Lines}
newsavebox{TitleBox}
newcommand*{SetupTitleBox}[2]{%
setbox#1vtop{%
bfseriescentering%
#2%
%strut%
par%
}%
}
newcommand*{BodyTextLarge}{%
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer id nisl porta, porttitor tellus sed, sollicitudin erat.
Aliquam tellus urna, feugiat et tortor quis, aliquam mattis orci.%
}%
newcommand*{BodyTextSmall}{%
Small Text.%
}%
newcommand{SelectedBodyText}{BodyTextLarge}
%newcommand{SelectedBodyText}{BodyTextSmall}
newsavebox{BodyBox}
newcommand{SetupBodyBox}[2]{%
setbox#1vtop{%
noindent#2%
%strut%
}%
}%
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
newlength{AdditionalSkip}
newcommand*{ShowTextGuideLines}[1]{%
begin{tikzpicture}[remember picture, overlay]
coordinate (X) at (current page text area.north west);
draw [draw=red, fill=yellow, fill opacity=0.2]
(X) circle (1pt);%% DEBUGGING: Ensure (X) is the correct spot.
setlength{AdditionalSkip}{DesiredSkipAboveTitle}%
foreach X in {1, ..., #1} {%
ifnumX>NumberOfTitleLines
%% After title: need to adjust AdditionalSkip for space after title
setlength{AdditionalSkip}{%
dimexprDesiredSkipAboveTitle+DesiredSkipBelowTitlerelax%
}%
fi
draw [thin, red] ([yshift=-Xbaselineskip-AdditionalSkip]X) -- ++ (hsize,0);
}%
tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
ifdefinedUseBoxes
node [Node Style, fill=yellow] at ([xshift=0.5hsize]X) {using boxes};
else
node [Node Style, fill=green] at ([xshift=0.5hsize]X) {Not using boxes};
fi
end{tikzpicture}%
}%
begin{document}
%% ---------------------------------------------------------- Set up the title and body
SetupTitleBox{TitleBox}{Title}%
SetupBodyBox{BodyBox}{SelectedBodyText}%
%% ---------------------------------------------------------- Title
hbox{}kern-topskip%% See https://tex.stackexchange.com/questions/7676/why-does-vspace0pt-add-vertical-space#comment12433_7681
vspace*{DesiredSkipAboveTitle}%
%%
ifdefinedUseBoxes
noindentcopyboxwithappropriatespace{TitleBox}%
vspace*{DesiredSkipBelowTitle}%
parnoindentcopyboxwithappropriatespace{BodyBox}%
else
noindent{bfseriescenteringTitlepar}%
vspace*{DesiredSkipBelowTitle}%
parnoindentSelectedBodyText%
fi
noindentShowTextGuideLines{7}%
end{document}

1
Thanks for the detailed explanation.
– Peter Grill
yesterday
You're welcome, and thank you for your generosity. The second bounty wasn't necessary.
– Circumscribe
yesterday
+1 but I believe there is an issue incopyboxwithappropriatespace: it should not "insert multiple of baselineskip closest to@tempdima" but "insert largest multiple of baselineskip less than@tempdima". And not set thedpof the inserted box to zero, but to the remainder of@tempdima(i.e. the actual depth of thevtopbox) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdimathendividecount@ by baselineskip. Thencount@has this largest integer and it timesbaselineskipis to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespaceisn't perfect and was really only intended for this specific use case. If your boxes contain just normal lines of text, then it doesn't really matter whether you round down or to the nearest integer and this missed depth isn't very relevant, but in general it would be better to do precisely what you say. (It is annoying thatdimexprdoesn't just round down btw…)
– Circumscribe
19 hours ago
I only just now thought of a better way to accomplish the same thing: you can create both avboxand avtopand then insert avtopwith the depth of thevboxand avspace*equal to the difference between their depths! This would also work if the distance between the baselines inside the box varies, (My answer is getting a little long though…)
– Circumscribe
19 hours ago
|
show 6 more comments
up vote
13
down vote
accepted
I think it would probably be useful to understand how TeX normally inserts vertical space between lines/boxes.
This will make it clear why this spacing is different when you wrap your paragraphs in a vbox, as in your MWE.
A method to achieve identical spacing with and without boxes can be found at the bottom this answer.
About baselineskip and lineskip
TeX normally tries to insert an appropriate amount of space between lines so that subsequent baselines are separated by baselineskip.
If it fails to do so without causing the distance between the boxes containing these lines to fall below lineskiplimit it'll insert lineskip instead.
Since lineskiplimit=0pt by default, this happens precisely when the boxes would otherwise overlap.
Because baselineskip=12pt, and lineskiplimit=0pt by default for a 10pt document, this means that if the distance between consecutive baselines is 12pt if the sum of the depth of the top line and the height of the bottom line is less than 12pt. Otherwise, the (boxes containing these) lines will be separated by lineskip (which is 1pt by default).
Here's an illustration:
parskip=0pt parindent=0pt
An ordinary line of text
par
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
par
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

Note that if you manually add additional skips, these are just added to whatever amount of space would otherwise be inserted:
parskip=0pt parindent=0pt
An ordinary line of text
parvspace{2mm}
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
parvspace{2mm}
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

The same thing, but with vertical boxes
It is no different for your vboxes. A vbox containing multiple lines has just a single baseline, which coincides with the baseline of the bottom line of its contents (top line for a vtop).
If TeX can't separate these baselines by exactly baselineskip without the boxes overlapping, it will completely ignore baselineskip, put a vertical space of lineskip between these boxes and call it a day.
This is the result: (green lines indicate the baselines)
parskip=0pt parindent=0pt
vbox{The first vbox containing\two lines}
par
vbox{The first vbox containing\two lines}

So the distance between the baseline of the second line of the first box and the baseline of the first line of the second box is equal to the depth of the former line plus the height of the latter line plus lineskip.
In this case I think that's 0pt + 6.94444pt + 1pt = 7.94444pt (the second number is how tall the letters h, d and b are).
Adding a strut or some character with descenders (gjpqy) to the first vbox would increase this distance by increasing the depth of the last line of this box.
The same thing happens between consecutive vtops:
parskip=0pt parindent=0pt
vtop{The first vtop containing\two lines}
par
vtop{The first vtop containing\two lines}

The only situation that likely matches your expected/desired outcome is the following:
parskip=0pt parindent=0pt
vbox{A vbox containing\two lines}
par
vtop{A vtop containing\two lines}

This happens because the baselines are close enough together that they can be separated by exactly baselineskip without the boxes overlapping.
The space above this vbox and below this vtop is again going to be "wrong", however.
The top of the page
Something similar happens at the top of the page.
TeX tries to insert a (positive) skip that wil separate the baseline of the first box on a page from the top of the text area by topskip. If it can't, it'll just place the box directly at the top.
The default value of topskip is equal to the font size (so 10pt for a 10pt document).
This is why it matters whether you put a vtop or a vbox at the top of your page.
documentclass{article}
usepackage[showframe]{geometry}
begin{document}
parskip=0pt parindent=0pt
vtop{A vtop containing\two lines}
clearpage
vbox{A vbox containing\two lines}
end{document}


In your MWE topskip isn't really relevant because you've inserted hbox{}kern-topskip. The hbox creates an empty box that will be separated from the top of the text area by the full topskip, and the subsequent kern undoes this skip.
It is thus as if there is an invisible paragraph at the top of your document that ends where the text area begins, so TeX tries to separate the first box (after this) from the top of the text area by baselineskip and insert lineskip if it can't, exactly as described above.
A possible solution
To get the correct spacing above a vertical box you should use vtop instead of vbox for the reasons outlined above.
To get the right amount of spacing below a vtop you can set its depth to zero and then insert a vertical skip just below the box equal to <n>baselineskip, where <n> is the number of lines in the box beyond the first. This skip can be calculated by taking the depth of the box and rounding down to a multiple baselineskip. You may want to set lineskiplimit=-maxdimen (inside the box, or globally) to ensure that all baselines inside the box are separated by exactly baselineskip.
The macro copyboxwithappropriatespace, which I define below (and which could use a better name), does almost precisely these two things. Instead of setting the depth to zero it sets it to the remainder of the division of the original depth by baselineskip (as suggested by jfbu in the comments).
This matters if the last line of your box is particularly deep or the first line of the next box is particularly high.
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
This command replaces copy (or usebox).
(If you want, you could define useboxwithappropriatespace by copying this definition, removing dp#1=@tempdimarelax and replacing copy by usebox in this definition.)
documentclass{article}
usepackage{showframe} %% <- show boundary of text area
usepackage{tikz} %% <- To draw the red brackets
usepackage{tikzpagenodes} %% <- to place them correctly
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
parindent=0pt %% <- saves me from having to type noindent
parskip=0pt %% <- default value is "0pt plus 1pt"
begin{document}
newsavebox{TitleBox}
newsavebox{BodyBox}
setboxTitleBox=vtop{textbf{A title that takes\up more than\two lines}}
setboxBodyBox=vtop{Lorem\ipsum}
copyboxwithappropriatespaceTitleBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
begin{tikzpicture}[remember picture, overlay,]
draw[red] (current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{topskip}}} +(-2pt,-topskip) -- +(0,-topskip);
foreach X in {0, ..., 7} {
draw[red] ([yshift=-topskip-Xbaselineskip]current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{baselineskip}}} +(-2pt,-baselineskip) -- +(0,-baselineskip);
}
end{tikzpicture}
clearpage
textbf{A title that takes\up more than\two lines}
Lorem\ipsum par Lorem\ipsum par Lorem\ipsum
end{document}
This produces two pages that look identical (apart from the fact that only the first has the red brackets) and which were constructed differently: one using vertical boxes and one using ordinary paragraphs.

Note that I've set parskip=0pt because the default value of parskip is not equal to 0pt but to 0pt plus 1pt.
This means that the amount of space between paragraphs can be stretched to up to 1pt to improve the page layout, which I'm guessing you don't want to happen.
In your MWE
The spacing in your MWE will be correct if you replace vbox by top and replace usebox by copyboxwithappropriatespace (and add the above definition to your preamble, of course):
defUseBoxes{}% Uncomment to use boxes
%% --------------------
documentclass{article}
usepackage{tikz}
usepackage{tikzpagenodes}
usepackage[paperwidth=7.0cm,showframe]{geometry}
newcommand*{DesiredSkipAboveTitle}{5pt}
newcommand*{DesiredSkipBelowTitle}{10pt}
newcommand*{NumberOfTitleLines}{2}
newcommand*{Title}{A Title that Takes Up Two Lines}
newsavebox{TitleBox}
newcommand*{SetupTitleBox}[2]{%
setbox#1vtop{%
bfseriescentering%
#2%
%strut%
par%
}%
}
newcommand*{BodyTextLarge}{%
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer id nisl porta, porttitor tellus sed, sollicitudin erat.
Aliquam tellus urna, feugiat et tortor quis, aliquam mattis orci.%
}%
newcommand*{BodyTextSmall}{%
Small Text.%
}%
newcommand{SelectedBodyText}{BodyTextLarge}
%newcommand{SelectedBodyText}{BodyTextSmall}
newsavebox{BodyBox}
newcommand{SetupBodyBox}[2]{%
setbox#1vtop{%
noindent#2%
%strut%
}%
}%
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
newlength{AdditionalSkip}
newcommand*{ShowTextGuideLines}[1]{%
begin{tikzpicture}[remember picture, overlay]
coordinate (X) at (current page text area.north west);
draw [draw=red, fill=yellow, fill opacity=0.2]
(X) circle (1pt);%% DEBUGGING: Ensure (X) is the correct spot.
setlength{AdditionalSkip}{DesiredSkipAboveTitle}%
foreach X in {1, ..., #1} {%
ifnumX>NumberOfTitleLines
%% After title: need to adjust AdditionalSkip for space after title
setlength{AdditionalSkip}{%
dimexprDesiredSkipAboveTitle+DesiredSkipBelowTitlerelax%
}%
fi
draw [thin, red] ([yshift=-Xbaselineskip-AdditionalSkip]X) -- ++ (hsize,0);
}%
tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
ifdefinedUseBoxes
node [Node Style, fill=yellow] at ([xshift=0.5hsize]X) {using boxes};
else
node [Node Style, fill=green] at ([xshift=0.5hsize]X) {Not using boxes};
fi
end{tikzpicture}%
}%
begin{document}
%% ---------------------------------------------------------- Set up the title and body
SetupTitleBox{TitleBox}{Title}%
SetupBodyBox{BodyBox}{SelectedBodyText}%
%% ---------------------------------------------------------- Title
hbox{}kern-topskip%% See https://tex.stackexchange.com/questions/7676/why-does-vspace0pt-add-vertical-space#comment12433_7681
vspace*{DesiredSkipAboveTitle}%
%%
ifdefinedUseBoxes
noindentcopyboxwithappropriatespace{TitleBox}%
vspace*{DesiredSkipBelowTitle}%
parnoindentcopyboxwithappropriatespace{BodyBox}%
else
noindent{bfseriescenteringTitlepar}%
vspace*{DesiredSkipBelowTitle}%
parnoindentSelectedBodyText%
fi
noindentShowTextGuideLines{7}%
end{document}

1
Thanks for the detailed explanation.
– Peter Grill
yesterday
You're welcome, and thank you for your generosity. The second bounty wasn't necessary.
– Circumscribe
yesterday
+1 but I believe there is an issue incopyboxwithappropriatespace: it should not "insert multiple of baselineskip closest to@tempdima" but "insert largest multiple of baselineskip less than@tempdima". And not set thedpof the inserted box to zero, but to the remainder of@tempdima(i.e. the actual depth of thevtopbox) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdimathendividecount@ by baselineskip. Thencount@has this largest integer and it timesbaselineskipis to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespaceisn't perfect and was really only intended for this specific use case. If your boxes contain just normal lines of text, then it doesn't really matter whether you round down or to the nearest integer and this missed depth isn't very relevant, but in general it would be better to do precisely what you say. (It is annoying thatdimexprdoesn't just round down btw…)
– Circumscribe
19 hours ago
I only just now thought of a better way to accomplish the same thing: you can create both avboxand avtopand then insert avtopwith the depth of thevboxand avspace*equal to the difference between their depths! This would also work if the distance between the baselines inside the box varies, (My answer is getting a little long though…)
– Circumscribe
19 hours ago
|
show 6 more comments
up vote
13
down vote
accepted
up vote
13
down vote
accepted
I think it would probably be useful to understand how TeX normally inserts vertical space between lines/boxes.
This will make it clear why this spacing is different when you wrap your paragraphs in a vbox, as in your MWE.
A method to achieve identical spacing with and without boxes can be found at the bottom this answer.
About baselineskip and lineskip
TeX normally tries to insert an appropriate amount of space between lines so that subsequent baselines are separated by baselineskip.
If it fails to do so without causing the distance between the boxes containing these lines to fall below lineskiplimit it'll insert lineskip instead.
Since lineskiplimit=0pt by default, this happens precisely when the boxes would otherwise overlap.
Because baselineskip=12pt, and lineskiplimit=0pt by default for a 10pt document, this means that if the distance between consecutive baselines is 12pt if the sum of the depth of the top line and the height of the bottom line is less than 12pt. Otherwise, the (boxes containing these) lines will be separated by lineskip (which is 1pt by default).
Here's an illustration:
parskip=0pt parindent=0pt
An ordinary line of text
par
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
par
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

Note that if you manually add additional skips, these are just added to whatever amount of space would otherwise be inserted:
parskip=0pt parindent=0pt
An ordinary line of text
parvspace{2mm}
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
parvspace{2mm}
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

The same thing, but with vertical boxes
It is no different for your vboxes. A vbox containing multiple lines has just a single baseline, which coincides with the baseline of the bottom line of its contents (top line for a vtop).
If TeX can't separate these baselines by exactly baselineskip without the boxes overlapping, it will completely ignore baselineskip, put a vertical space of lineskip between these boxes and call it a day.
This is the result: (green lines indicate the baselines)
parskip=0pt parindent=0pt
vbox{The first vbox containing\two lines}
par
vbox{The first vbox containing\two lines}

So the distance between the baseline of the second line of the first box and the baseline of the first line of the second box is equal to the depth of the former line plus the height of the latter line plus lineskip.
In this case I think that's 0pt + 6.94444pt + 1pt = 7.94444pt (the second number is how tall the letters h, d and b are).
Adding a strut or some character with descenders (gjpqy) to the first vbox would increase this distance by increasing the depth of the last line of this box.
The same thing happens between consecutive vtops:
parskip=0pt parindent=0pt
vtop{The first vtop containing\two lines}
par
vtop{The first vtop containing\two lines}

The only situation that likely matches your expected/desired outcome is the following:
parskip=0pt parindent=0pt
vbox{A vbox containing\two lines}
par
vtop{A vtop containing\two lines}

This happens because the baselines are close enough together that they can be separated by exactly baselineskip without the boxes overlapping.
The space above this vbox and below this vtop is again going to be "wrong", however.
The top of the page
Something similar happens at the top of the page.
TeX tries to insert a (positive) skip that wil separate the baseline of the first box on a page from the top of the text area by topskip. If it can't, it'll just place the box directly at the top.
The default value of topskip is equal to the font size (so 10pt for a 10pt document).
This is why it matters whether you put a vtop or a vbox at the top of your page.
documentclass{article}
usepackage[showframe]{geometry}
begin{document}
parskip=0pt parindent=0pt
vtop{A vtop containing\two lines}
clearpage
vbox{A vbox containing\two lines}
end{document}


In your MWE topskip isn't really relevant because you've inserted hbox{}kern-topskip. The hbox creates an empty box that will be separated from the top of the text area by the full topskip, and the subsequent kern undoes this skip.
It is thus as if there is an invisible paragraph at the top of your document that ends where the text area begins, so TeX tries to separate the first box (after this) from the top of the text area by baselineskip and insert lineskip if it can't, exactly as described above.
A possible solution
To get the correct spacing above a vertical box you should use vtop instead of vbox for the reasons outlined above.
To get the right amount of spacing below a vtop you can set its depth to zero and then insert a vertical skip just below the box equal to <n>baselineskip, where <n> is the number of lines in the box beyond the first. This skip can be calculated by taking the depth of the box and rounding down to a multiple baselineskip. You may want to set lineskiplimit=-maxdimen (inside the box, or globally) to ensure that all baselines inside the box are separated by exactly baselineskip.
The macro copyboxwithappropriatespace, which I define below (and which could use a better name), does almost precisely these two things. Instead of setting the depth to zero it sets it to the remainder of the division of the original depth by baselineskip (as suggested by jfbu in the comments).
This matters if the last line of your box is particularly deep or the first line of the next box is particularly high.
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
This command replaces copy (or usebox).
(If you want, you could define useboxwithappropriatespace by copying this definition, removing dp#1=@tempdimarelax and replacing copy by usebox in this definition.)
documentclass{article}
usepackage{showframe} %% <- show boundary of text area
usepackage{tikz} %% <- To draw the red brackets
usepackage{tikzpagenodes} %% <- to place them correctly
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
parindent=0pt %% <- saves me from having to type noindent
parskip=0pt %% <- default value is "0pt plus 1pt"
begin{document}
newsavebox{TitleBox}
newsavebox{BodyBox}
setboxTitleBox=vtop{textbf{A title that takes\up more than\two lines}}
setboxBodyBox=vtop{Lorem\ipsum}
copyboxwithappropriatespaceTitleBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
begin{tikzpicture}[remember picture, overlay,]
draw[red] (current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{topskip}}} +(-2pt,-topskip) -- +(0,-topskip);
foreach X in {0, ..., 7} {
draw[red] ([yshift=-topskip-Xbaselineskip]current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{baselineskip}}} +(-2pt,-baselineskip) -- +(0,-baselineskip);
}
end{tikzpicture}
clearpage
textbf{A title that takes\up more than\two lines}
Lorem\ipsum par Lorem\ipsum par Lorem\ipsum
end{document}
This produces two pages that look identical (apart from the fact that only the first has the red brackets) and which were constructed differently: one using vertical boxes and one using ordinary paragraphs.

Note that I've set parskip=0pt because the default value of parskip is not equal to 0pt but to 0pt plus 1pt.
This means that the amount of space between paragraphs can be stretched to up to 1pt to improve the page layout, which I'm guessing you don't want to happen.
In your MWE
The spacing in your MWE will be correct if you replace vbox by top and replace usebox by copyboxwithappropriatespace (and add the above definition to your preamble, of course):
defUseBoxes{}% Uncomment to use boxes
%% --------------------
documentclass{article}
usepackage{tikz}
usepackage{tikzpagenodes}
usepackage[paperwidth=7.0cm,showframe]{geometry}
newcommand*{DesiredSkipAboveTitle}{5pt}
newcommand*{DesiredSkipBelowTitle}{10pt}
newcommand*{NumberOfTitleLines}{2}
newcommand*{Title}{A Title that Takes Up Two Lines}
newsavebox{TitleBox}
newcommand*{SetupTitleBox}[2]{%
setbox#1vtop{%
bfseriescentering%
#2%
%strut%
par%
}%
}
newcommand*{BodyTextLarge}{%
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer id nisl porta, porttitor tellus sed, sollicitudin erat.
Aliquam tellus urna, feugiat et tortor quis, aliquam mattis orci.%
}%
newcommand*{BodyTextSmall}{%
Small Text.%
}%
newcommand{SelectedBodyText}{BodyTextLarge}
%newcommand{SelectedBodyText}{BodyTextSmall}
newsavebox{BodyBox}
newcommand{SetupBodyBox}[2]{%
setbox#1vtop{%
noindent#2%
%strut%
}%
}%
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
newlength{AdditionalSkip}
newcommand*{ShowTextGuideLines}[1]{%
begin{tikzpicture}[remember picture, overlay]
coordinate (X) at (current page text area.north west);
draw [draw=red, fill=yellow, fill opacity=0.2]
(X) circle (1pt);%% DEBUGGING: Ensure (X) is the correct spot.
setlength{AdditionalSkip}{DesiredSkipAboveTitle}%
foreach X in {1, ..., #1} {%
ifnumX>NumberOfTitleLines
%% After title: need to adjust AdditionalSkip for space after title
setlength{AdditionalSkip}{%
dimexprDesiredSkipAboveTitle+DesiredSkipBelowTitlerelax%
}%
fi
draw [thin, red] ([yshift=-Xbaselineskip-AdditionalSkip]X) -- ++ (hsize,0);
}%
tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
ifdefinedUseBoxes
node [Node Style, fill=yellow] at ([xshift=0.5hsize]X) {using boxes};
else
node [Node Style, fill=green] at ([xshift=0.5hsize]X) {Not using boxes};
fi
end{tikzpicture}%
}%
begin{document}
%% ---------------------------------------------------------- Set up the title and body
SetupTitleBox{TitleBox}{Title}%
SetupBodyBox{BodyBox}{SelectedBodyText}%
%% ---------------------------------------------------------- Title
hbox{}kern-topskip%% See https://tex.stackexchange.com/questions/7676/why-does-vspace0pt-add-vertical-space#comment12433_7681
vspace*{DesiredSkipAboveTitle}%
%%
ifdefinedUseBoxes
noindentcopyboxwithappropriatespace{TitleBox}%
vspace*{DesiredSkipBelowTitle}%
parnoindentcopyboxwithappropriatespace{BodyBox}%
else
noindent{bfseriescenteringTitlepar}%
vspace*{DesiredSkipBelowTitle}%
parnoindentSelectedBodyText%
fi
noindentShowTextGuideLines{7}%
end{document}

I think it would probably be useful to understand how TeX normally inserts vertical space between lines/boxes.
This will make it clear why this spacing is different when you wrap your paragraphs in a vbox, as in your MWE.
A method to achieve identical spacing with and without boxes can be found at the bottom this answer.
About baselineskip and lineskip
TeX normally tries to insert an appropriate amount of space between lines so that subsequent baselines are separated by baselineskip.
If it fails to do so without causing the distance between the boxes containing these lines to fall below lineskiplimit it'll insert lineskip instead.
Since lineskiplimit=0pt by default, this happens precisely when the boxes would otherwise overlap.
Because baselineskip=12pt, and lineskiplimit=0pt by default for a 10pt document, this means that if the distance between consecutive baselines is 12pt if the sum of the depth of the top line and the height of the bottom line is less than 12pt. Otherwise, the (boxes containing these) lines will be separated by lineskip (which is 1pt by default).
Here's an illustration:
parskip=0pt parindent=0pt
An ordinary line of text
par
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
par
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

Note that if you manually add additional skips, these are just added to whatever amount of space would otherwise be inserted:
parskip=0pt parindent=0pt
An ordinary line of text
parvspace{2mm}
Another ordinary line of text
vspace{baselineskip}
rule[-5pt]{10pt}{15pt}This line has a depth of texttt{5pt}
parvspace{2mm}
rule[-5pt]{10pt}{15pt}This line has a height of texttt{10pt}

The same thing, but with vertical boxes
It is no different for your vboxes. A vbox containing multiple lines has just a single baseline, which coincides with the baseline of the bottom line of its contents (top line for a vtop).
If TeX can't separate these baselines by exactly baselineskip without the boxes overlapping, it will completely ignore baselineskip, put a vertical space of lineskip between these boxes and call it a day.
This is the result: (green lines indicate the baselines)
parskip=0pt parindent=0pt
vbox{The first vbox containing\two lines}
par
vbox{The first vbox containing\two lines}

So the distance between the baseline of the second line of the first box and the baseline of the first line of the second box is equal to the depth of the former line plus the height of the latter line plus lineskip.
In this case I think that's 0pt + 6.94444pt + 1pt = 7.94444pt (the second number is how tall the letters h, d and b are).
Adding a strut or some character with descenders (gjpqy) to the first vbox would increase this distance by increasing the depth of the last line of this box.
The same thing happens between consecutive vtops:
parskip=0pt parindent=0pt
vtop{The first vtop containing\two lines}
par
vtop{The first vtop containing\two lines}

The only situation that likely matches your expected/desired outcome is the following:
parskip=0pt parindent=0pt
vbox{A vbox containing\two lines}
par
vtop{A vtop containing\two lines}

This happens because the baselines are close enough together that they can be separated by exactly baselineskip without the boxes overlapping.
The space above this vbox and below this vtop is again going to be "wrong", however.
The top of the page
Something similar happens at the top of the page.
TeX tries to insert a (positive) skip that wil separate the baseline of the first box on a page from the top of the text area by topskip. If it can't, it'll just place the box directly at the top.
The default value of topskip is equal to the font size (so 10pt for a 10pt document).
This is why it matters whether you put a vtop or a vbox at the top of your page.
documentclass{article}
usepackage[showframe]{geometry}
begin{document}
parskip=0pt parindent=0pt
vtop{A vtop containing\two lines}
clearpage
vbox{A vbox containing\two lines}
end{document}


In your MWE topskip isn't really relevant because you've inserted hbox{}kern-topskip. The hbox creates an empty box that will be separated from the top of the text area by the full topskip, and the subsequent kern undoes this skip.
It is thus as if there is an invisible paragraph at the top of your document that ends where the text area begins, so TeX tries to separate the first box (after this) from the top of the text area by baselineskip and insert lineskip if it can't, exactly as described above.
A possible solution
To get the correct spacing above a vertical box you should use vtop instead of vbox for the reasons outlined above.
To get the right amount of spacing below a vtop you can set its depth to zero and then insert a vertical skip just below the box equal to <n>baselineskip, where <n> is the number of lines in the box beyond the first. This skip can be calculated by taking the depth of the box and rounding down to a multiple baselineskip. You may want to set lineskiplimit=-maxdimen (inside the box, or globally) to ensure that all baselines inside the box are separated by exactly baselineskip.
The macro copyboxwithappropriatespace, which I define below (and which could use a better name), does almost precisely these two things. Instead of setting the depth to zero it sets it to the remainder of the division of the original depth by baselineskip (as suggested by jfbu in the comments).
This matters if the last line of your box is particularly deep or the first line of the next box is particularly high.
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
This command replaces copy (or usebox).
(If you want, you could define useboxwithappropriatespace by copying this definition, removing dp#1=@tempdimarelax and replacing copy by usebox in this definition.)
documentclass{article}
usepackage{showframe} %% <- show boundary of text area
usepackage{tikz} %% <- To draw the red brackets
usepackage{tikzpagenodes} %% <- to place them correctly
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
parindent=0pt %% <- saves me from having to type noindent
parskip=0pt %% <- default value is "0pt plus 1pt"
begin{document}
newsavebox{TitleBox}
newsavebox{BodyBox}
setboxTitleBox=vtop{textbf{A title that takes\up more than\two lines}}
setboxBodyBox=vtop{Lorem\ipsum}
copyboxwithappropriatespaceTitleBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
copyboxwithappropriatespaceBodyBox
begin{tikzpicture}[remember picture, overlay,]
draw[red] (current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{topskip}}} +(-2pt,-topskip) -- +(0,-topskip);
foreach X in {0, ..., 7} {
draw[red] ([yshift=-topskip-Xbaselineskip]current page text area.north west) -- +(-2pt,0)
-- node[left]{texttt{detokenize{baselineskip}}} +(-2pt,-baselineskip) -- +(0,-baselineskip);
}
end{tikzpicture}
clearpage
textbf{A title that takes\up more than\two lines}
Lorem\ipsum par Lorem\ipsum par Lorem\ipsum
end{document}
This produces two pages that look identical (apart from the fact that only the first has the red brackets) and which were constructed differently: one using vertical boxes and one using ordinary paragraphs.

Note that I've set parskip=0pt because the default value of parskip is not equal to 0pt but to 0pt plus 1pt.
This means that the amount of space between paragraphs can be stretched to up to 1pt to improve the page layout, which I'm guessing you don't want to happen.
In your MWE
The spacing in your MWE will be correct if you replace vbox by top and replace usebox by copyboxwithappropriatespace (and add the above definition to your preamble, of course):
defUseBoxes{}% Uncomment to use boxes
%% --------------------
documentclass{article}
usepackage{tikz}
usepackage{tikzpagenodes}
usepackage[paperwidth=7.0cm,showframe]{geometry}
newcommand*{DesiredSkipAboveTitle}{5pt}
newcommand*{DesiredSkipBelowTitle}{10pt}
newcommand*{NumberOfTitleLines}{2}
newcommand*{Title}{A Title that Takes Up Two Lines}
newsavebox{TitleBox}
newcommand*{SetupTitleBox}[2]{%
setbox#1vtop{%
bfseriescentering%
#2%
%strut%
par%
}%
}
newcommand*{BodyTextLarge}{%
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer id nisl porta, porttitor tellus sed, sollicitudin erat.
Aliquam tellus urna, feugiat et tortor quis, aliquam mattis orci.%
}%
newcommand*{BodyTextSmall}{%
Small Text.%
}%
newcommand{SelectedBodyText}{BodyTextLarge}
%newcommand{SelectedBodyText}{BodyTextSmall}
newsavebox{BodyBox}
newcommand{SetupBodyBox}[2]{%
setbox#1vtop{%
noindent#2%
%strut%
}%
}%
makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
newcommand*copyboxwithappropriatespace[1]{%
begingroup
@tempdima=dp#1relax %% <-- store the depth in @tempdima
count@=@tempdimarelax %% <-- convert to a number
dividecount@ by baselineskip %% <-- divide by baselineskip
dp#1=dimexpr@tempdima-count@baselineskip %% <-- set the depth to to the remainder
copy#1% %% <-- insert the box
dp#1=@tempdimarelax %% <-- reset its depth
vspace*{count@baselineskip}%
endgroup %% ^-- insert count@ baselineskips
}
makeatother %% <-- revert @
newlength{AdditionalSkip}
newcommand*{ShowTextGuideLines}[1]{%
begin{tikzpicture}[remember picture, overlay]
coordinate (X) at (current page text area.north west);
draw [draw=red, fill=yellow, fill opacity=0.2]
(X) circle (1pt);%% DEBUGGING: Ensure (X) is the correct spot.
setlength{AdditionalSkip}{DesiredSkipAboveTitle}%
foreach X in {1, ..., #1} {%
ifnumX>NumberOfTitleLines
%% After title: need to adjust AdditionalSkip for space after title
setlength{AdditionalSkip}{%
dimexprDesiredSkipAboveTitle+DesiredSkipBelowTitlerelax%
}%
fi
draw [thin, red] ([yshift=-Xbaselineskip-AdditionalSkip]X) -- ++ (hsize,0);
}%
tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
ifdefinedUseBoxes
node [Node Style, fill=yellow] at ([xshift=0.5hsize]X) {using boxes};
else
node [Node Style, fill=green] at ([xshift=0.5hsize]X) {Not using boxes};
fi
end{tikzpicture}%
}%
begin{document}
%% ---------------------------------------------------------- Set up the title and body
SetupTitleBox{TitleBox}{Title}%
SetupBodyBox{BodyBox}{SelectedBodyText}%
%% ---------------------------------------------------------- Title
hbox{}kern-topskip%% See https://tex.stackexchange.com/questions/7676/why-does-vspace0pt-add-vertical-space#comment12433_7681
vspace*{DesiredSkipAboveTitle}%
%%
ifdefinedUseBoxes
noindentcopyboxwithappropriatespace{TitleBox}%
vspace*{DesiredSkipBelowTitle}%
parnoindentcopyboxwithappropriatespace{BodyBox}%
else
noindent{bfseriescenteringTitlepar}%
vspace*{DesiredSkipBelowTitle}%
parnoindentSelectedBodyText%
fi
noindentShowTextGuideLines{7}%
end{document}

edited 17 hours ago
answered Dec 7 at 21:35
Circumscribe
3,8371429
3,8371429
1
Thanks for the detailed explanation.
– Peter Grill
yesterday
You're welcome, and thank you for your generosity. The second bounty wasn't necessary.
– Circumscribe
yesterday
+1 but I believe there is an issue incopyboxwithappropriatespace: it should not "insert multiple of baselineskip closest to@tempdima" but "insert largest multiple of baselineskip less than@tempdima". And not set thedpof the inserted box to zero, but to the remainder of@tempdima(i.e. the actual depth of thevtopbox) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdimathendividecount@ by baselineskip. Thencount@has this largest integer and it timesbaselineskipis to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespaceisn't perfect and was really only intended for this specific use case. If your boxes contain just normal lines of text, then it doesn't really matter whether you round down or to the nearest integer and this missed depth isn't very relevant, but in general it would be better to do precisely what you say. (It is annoying thatdimexprdoesn't just round down btw…)
– Circumscribe
19 hours ago
I only just now thought of a better way to accomplish the same thing: you can create both avboxand avtopand then insert avtopwith the depth of thevboxand avspace*equal to the difference between their depths! This would also work if the distance between the baselines inside the box varies, (My answer is getting a little long though…)
– Circumscribe
19 hours ago
|
show 6 more comments
1
Thanks for the detailed explanation.
– Peter Grill
yesterday
You're welcome, and thank you for your generosity. The second bounty wasn't necessary.
– Circumscribe
yesterday
+1 but I believe there is an issue incopyboxwithappropriatespace: it should not "insert multiple of baselineskip closest to@tempdima" but "insert largest multiple of baselineskip less than@tempdima". And not set thedpof the inserted box to zero, but to the remainder of@tempdima(i.e. the actual depth of thevtopbox) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdimathendividecount@ by baselineskip. Thencount@has this largest integer and it timesbaselineskipis to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespaceisn't perfect and was really only intended for this specific use case. If your boxes contain just normal lines of text, then it doesn't really matter whether you round down or to the nearest integer and this missed depth isn't very relevant, but in general it would be better to do precisely what you say. (It is annoying thatdimexprdoesn't just round down btw…)
– Circumscribe
19 hours ago
I only just now thought of a better way to accomplish the same thing: you can create both avboxand avtopand then insert avtopwith the depth of thevboxand avspace*equal to the difference between their depths! This would also work if the distance between the baselines inside the box varies, (My answer is getting a little long though…)
– Circumscribe
19 hours ago
1
1
Thanks for the detailed explanation.
– Peter Grill
yesterday
Thanks for the detailed explanation.
– Peter Grill
yesterday
You're welcome, and thank you for your generosity. The second bounty wasn't necessary.
– Circumscribe
yesterday
You're welcome, and thank you for your generosity. The second bounty wasn't necessary.
– Circumscribe
yesterday
+1 but I believe there is an issue in
copyboxwithappropriatespace: it should not "insert multiple of baselineskip closest to @tempdima" but "insert largest multiple of baselineskip less than @tempdima". And not set the dp of the inserted box to zero, but to the remainder of @tempdima (i.e. the actual depth of the vtop box) after removing "largest multiple of baselineskip". To obtain that largest integer you can do count@@tempdima then dividecount@ by baselineskip. Then count@ has this largest integer and it times baselineskip is to be removed.– jfbu
22 hours ago
+1 but I believe there is an issue in
copyboxwithappropriatespace: it should not "insert multiple of baselineskip closest to @tempdima" but "insert largest multiple of baselineskip less than @tempdima". And not set the dp of the inserted box to zero, but to the remainder of @tempdima (i.e. the actual depth of the vtop box) after removing "largest multiple of baselineskip". To obtain that largest integer you can do count@@tempdima then dividecount@ by baselineskip. Then count@ has this largest integer and it times baselineskip is to be removed.– jfbu
22 hours ago
@jfbu: You're right. The definition of
copyboxwithappropriatespace isn't perfect and was really only intended for this specific use case. If your boxes contain just normal lines of text, then it doesn't really matter whether you round down or to the nearest integer and this missed depth isn't very relevant, but in general it would be better to do precisely what you say. (It is annoying that dimexpr doesn't just round down btw…)– Circumscribe
19 hours ago
@jfbu: You're right. The definition of
copyboxwithappropriatespace isn't perfect and was really only intended for this specific use case. If your boxes contain just normal lines of text, then it doesn't really matter whether you round down or to the nearest integer and this missed depth isn't very relevant, but in general it would be better to do precisely what you say. (It is annoying that dimexpr doesn't just round down btw…)– Circumscribe
19 hours ago
I only just now thought of a better way to accomplish the same thing: you can create both a
vbox and a vtop and then insert a vtop with the depth of the vbox and a vspace* equal to the difference between their depths! This would also work if the distance between the baselines inside the box varies, (My answer is getting a little long though…)– Circumscribe
19 hours ago
I only just now thought of a better way to accomplish the same thing: you can create both a
vbox and a vtop and then insert a vtop with the depth of the vbox and a vspace* equal to the difference between their depths! This would also work if the distance between the baselines inside the box varies, (My answer is getting a little long though…)– Circumscribe
19 hours ago
|
show 6 more comments
Thanks for contributing an answer to TeX - LaTeX Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f463039%2fspacing-difference-when-using-boxes%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
you used vbox rather than vtop (not that either of them are really latex)
– David Carlisle
Dec 3 at 20:41
@DavidCarlisle: Using
vtopinstead ofvboxseems to resolve the issue with theTitleBox, but not theBodyBox?– Peter Grill
Dec 3 at 20:57
2
I didn't actually run the code, I'll look in a bit:-) but getting spacing around boxes right is tricky you need to control the first and last baseline but boxes only have one reference point, hence latex3 coffins, boxes with handles.
– David Carlisle
Dec 3 at 20:58