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 newsavebox
s (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
struts
to 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 astrut
at the end of the body text, but that does not seem to work here.
Notes:
- A vertical space defined by
DesiredSkipAboveTitle
is added before the title and aDesiredSkipBelowTitle
is added after the title. - Need to run at least twice to get the
tikz
drawing in correct position. - The
adjustbox
package 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 theTitleBox
needs araise=-1.3pt
option and theBodyBox
requires araise=-13.3pt
with 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 newsavebox
s (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
struts
to 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 astrut
at the end of the body text, but that does not seem to work here.
Notes:
- A vertical space defined by
DesiredSkipAboveTitle
is added before the title and aDesiredSkipBelowTitle
is added after the title. - Need to run at least twice to get the
tikz
drawing in correct position. - The
adjustbox
package 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 theTitleBox
needs araise=-1.3pt
option and theBodyBox
requires araise=-13.3pt
with 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: Usingvtop
instead ofvbox
seems 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 newsavebox
s (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
struts
to 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 astrut
at the end of the body text, but that does not seem to work here.
Notes:
- A vertical space defined by
DesiredSkipAboveTitle
is added before the title and aDesiredSkipBelowTitle
is added after the title. - Need to run at least twice to get the
tikz
drawing in correct position. - The
adjustbox
package 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 theTitleBox
needs araise=-1.3pt
option and theBodyBox
requires araise=-13.3pt
with 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 newsavebox
s (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
struts
to 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 astrut
at the end of the body text, but that does not seem to work here.
Notes:
- A vertical space defined by
DesiredSkipAboveTitle
is added before the title and aDesiredSkipBelowTitle
is added after the title. - Need to run at least twice to get the
tikz
drawing in correct position. - The
adjustbox
package 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 theTitleBox
needs araise=-1.3pt
option and theBodyBox
requires araise=-13.3pt
with 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: Usingvtop
instead ofvbox
seems 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: Usingvtop
instead ofvbox
seems 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 vbox
es. 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 vtop
s:
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 thedp
of the inserted box to zero, but to the remainder of@tempdima
(i.e. the actual depth of thevtop
box) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdima
thendividecount@ by baselineskip
. Thencount@
has this largest integer and it timesbaselineskip
is to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespace
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 thatdimexpr
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 avbox
and avtop
and then insert avtop
with the depth of thevbox
and 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 vbox
es. 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 vtop
s:
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 thedp
of the inserted box to zero, but to the remainder of@tempdima
(i.e. the actual depth of thevtop
box) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdima
thendividecount@ by baselineskip
. Thencount@
has this largest integer and it timesbaselineskip
is to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespace
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 thatdimexpr
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 avbox
and avtop
and then insert avtop
with the depth of thevbox
and 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 vbox
es. 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 vtop
s:
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 thedp
of the inserted box to zero, but to the remainder of@tempdima
(i.e. the actual depth of thevtop
box) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdima
thendividecount@ by baselineskip
. Thencount@
has this largest integer and it timesbaselineskip
is to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespace
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 thatdimexpr
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 avbox
and avtop
and then insert avtop
with the depth of thevbox
and 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 vbox
es. 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 vtop
s:
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 vbox
es. 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 vtop
s:
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 thedp
of the inserted box to zero, but to the remainder of@tempdima
(i.e. the actual depth of thevtop
box) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdima
thendividecount@ by baselineskip
. Thencount@
has this largest integer and it timesbaselineskip
is to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespace
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 thatdimexpr
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 avbox
and avtop
and then insert avtop
with the depth of thevbox
and 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 thedp
of the inserted box to zero, but to the remainder of@tempdima
(i.e. the actual depth of thevtop
box) after removing "largest multiple of baselineskip". To obtain that largest integer you can docount@@tempdima
thendividecount@ by baselineskip
. Thencount@
has this largest integer and it timesbaselineskip
is to be removed.
– jfbu
22 hours ago
@jfbu: You're right. The definition ofcopyboxwithappropriatespace
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 thatdimexpr
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 avbox
and avtop
and then insert avtop
with the depth of thevbox
and 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
vtop
instead ofvbox
seems 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