Any box commands in math mode need to be set in all styles so TeX can pick the right one later; the \mathchoice
command is what you need here. See my rant against \over
on this site the other day:-) If you just use \hbox{$...$}
you are starting a fresh math list at text size even in subscript position.
You can re-assert the correct spacing around your construct using \mathbin
\mathop
and friends, This could be done automatically by looking up the\mathcode
of the character you are boxing. Inspecting bm.sty
might be helpful, which does this to re-assert the correct math class after making things bold, but the issues are similar.
It's a bit late to re-create bm
interrogation of \mathcode
so this just looks at special cases \cdot
and +
but otherwise does something plausible I suspect
UPDATE At the end I place a version that does automatically pick up the math class from character tokens, things like \cdot
defined by \mathchar
and things like \log
with explicit \mathop
. I will leave the original version in place as the math class detection probably obscures the logic of the measuring which was perhaps the main part of the question.
\documentclass{article}
\usepackage{amsmath,color}
\makeatletter
\def\fb@cdot{\cdot}
\def\fb@p{+}
\def\@mfbox#1{%
\def\fb@{#1}%
\ifx\fb@\fb@cdot\mathbin\fi
\ifx\fb@\fb@p\mathbin\fi
{\text{\fboxsep\z@\colorbox{yellow}{$\m@th#1$}}}}
\def\@tfbox#1{{\fboxsep\z@\colorbox{yellow}{#1}}}
\def\fillbox#1{\ifmmode\expandafter\@mfbox\else\expandafter\@tfbox\fi{#1}}
\begin{document}
\vskip1em
$\fillbox{p}_{\fillbox{x}}
\mathrel{\stackrel{\fillbox{_{~+}}}{\fillbox{\leftarrow}}}
\fillbox{(}\fillbox{\frac{\fillbox{1}}{\fillbox{2}}}\fillbox{\cdot}
\fillbox{a}_{\fillbox{x}}\fillbox{\cdot}%
\fillbox{\Delta}\fillbox{t}^{\fillbox{2}}\fillbox{)} \fillbox{+}
\fillbox{(}\fillbox{v}_{\fillbox{x}}\fillbox{\cdot}%
\fillbox{\Delta}\fillbox{t}\fillbox{)}$\fillbox{;}\par
$\fillbox{v}_{\fillbox{x}}
\mathrel{\stackrel{\fillbox{_{~+}}}{\fillbox{\leftarrow}}}
\fillbox{a}_{\fillbox{x}}\fillbox{\cdot}%
\fillbox{\Delta}\fillbox{t}$\fillbox{;}\par
\vskip1em
$p_x \mathrel{\stackrel{_{~+}}{\leftarrow}}
(\frac{1}{2}\cdot a_x\cdot\Delta t^2) + (v_x\cdot\Delta t)$;\par
$v_x \mathrel{\stackrel{_{~+}}{\leftarrow}} a_x\cdot\Delta t$;\par
\end{document}
Version using bm
-style math class detection.
\documentclass{article}
\usepackage{amsmath,color}
\makeatletter
\def\fb@eat#1#2#3#4#5{\futurelet\fb@let@token\fb@eat@}
\def\fb@eat@#1\fb@eat{%
\ifx\fb@let@token\bgroup
\else\ifx\fb@let@token\mathop
\mathop
\else\ifx\fb@let@token\mathbin
\mathbin
\else\ifx\fb@let@token\mathrel
\mathrel
\else\ifx\fb@let@token\mathopen
\mathopen
\else\ifx\fb@let@token\mathop
\mathop
\else\ifx\fb@let@token\mathpunct
\mathpunct
\else\ifcat.\ifcat a\noexpand\fb@let@token.\else\noexpand\fb@let@token\fi
\afterassignment\fb@mathchar\count@\mathcode`#1\relax\fb@eat
\else\ifx\fb@let@token\mathchar
\afterassignment\fb@mathchar\expandafter\count@\@gobble#1\relax\fb@eat
\else
\xdef\meaning@{\meaning\fb@let@token}%
\expandafter\fb@mchar@test\meaning@""\@nil
\fi\fi\fi\fi\fi\fi\fi\fi\fi
}
\def\@mfbox#1{%
\begingroup
\let\protect\empty
\expandafter\fb@eat\romannumeral`\Q#1\relax\fb@eat
\ifcase\count@
\or
\mathop\or
\mathbin\or
\mathrel\or
\mathopen\or
\mathclose\or
\mathpunct\or
\fi
{\text{\fboxsep\z@\colorbox{yellow}{$\m@th#1$}}}%
\endgroup}
\edef\fb@mchar@{\meaning\mathchar}
\def\fb@mchar@test#1"#2"#3\@nil{%
\xdef\meaning@{#1}%
\ifx\meaning@\fb@mchar@
\count@"#2\relax
\fb@mathchar\fb@eat
\fi
}
\def\fb@mathchar#1\fb@eat{%
\divide\count@"1000 }
\def\@tfbox#1{{\fboxsep\z@\colorbox{yellow}{#1}}}
\def\fillbox#1{\ifmmode\expandafter\@mfbox\else\expandafter\@tfbox\fi{#1}}
\begin{document}
$a-b$
$a{-}b$
$a\fillbox{-}b$
$\log x + \mathrm{log}x$
$\fillbox{\log} \fillbox{x} \fillbox{+} \fillbox{\mathrm{log}}\fillbox{x}$
$\fillbox{0}$
$ a \cdot b {\cdot} c$
$ a \fillbox{\cdot} b \fillbox{{\cdot}}c $
$a \fillbox{\mathchar"2201} b \fillbox{{\mathchar"2201}} c $
$ a - b < \alpha $
$\fillbox{a} \fillbox{-} \fillbox{b} \fillbox{<} \fillbox{\alpha}$
$\fillbox{a+b}$
$a = \sqrt{h}$
$a = \fillbox{\sqrt{h}}$
\vskip1em
$\fillbox{p}_{\fillbox{x}}
\mathrel{\stackrel{\fillbox{_{~+}}}{\fillbox{\leftarrow}}}
\fillbox{(}\fillbox{\frac{\fillbox{1}}{\fillbox{2}}}\fillbox{\cdot}
\fillbox{a}_{\fillbox{x}}\fillbox{\cdot}%
\fillbox{\Delta}\fillbox{t}^{\fillbox{2}}\fillbox{)} \fillbox{+}
\fillbox{(}\fillbox{v}_{\fillbox{x}}\fillbox{\cdot}%
\fillbox{\Delta}\fillbox{t}\fillbox{)}$\fillbox{;}\par
$\fillbox{v}_{\fillbox{x}}
\mathrel{\stackrel{\fillbox{_{~+}}}{\fillbox{\leftarrow}}}
\fillbox{a}_{\fillbox{x}}\fillbox{\cdot}%
\fillbox{\Delta}\fillbox{t}$\fillbox{;}\par
\vskip1em
$p_x \mathrel{\stackrel{_{~+}}{\leftarrow}}
(\frac{1}{2}\cdot a_x\cdot\Delta t^2) + (v_x\cdot\Delta t)$;\par
$v_x \mathrel{\stackrel{_{~+}}{\leftarrow}} a_x\cdot\Delta t$;\par
\end{document}
Producing
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
\par\vspace{2mm}
Another ordinary line of text
\vspace{\baselineskip}
\rule[-5pt]{10pt}{15pt}This line has a depth of \texttt{5pt}
\par\vspace{2mm}
\rule[-5pt]{10pt}{15pt}This line has a height of \texttt{10pt}
(Remark: normally, these lines would be separated by an additional \parskip
, but I've set this length to zero.)
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.
(Remark: \vbox
es inserted in vertical mode are not preceded by a \parskip
. If you do want this skip you should either insert it manually (using \vspace{\parskip}
) or use \noindent
to switch to horizontal mode before inserting the box.)
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 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 equal to the depth of the last line and then insert a vertical skip equal to the distance between the first and the last baseline.
The command \insertvbox
, which I define below, is intended to replace \usebox
in your MWE (except it should not be preceded by \noindent
). It wraps the contents of a \vbox
(or \vtop
or \vcenter
, it makes no difference) you supply in both a \vtop
and a \vbox
, gives the \vtop
the depth of the \vbox
, prints this \vtop
and inserts a vertical space to compensate for the decrease in depth.
Note: Because I'm using \unvcopy
, code inside the box is only executed once. The double boxing problem pointed out by jfbu in this comment therefore does not occur.
\makeatletter
\newcommand*\insertvbox[1]{% %% <- #1 can be a vbox, vtop or vcenter
\begingroup %% <- limits scope of assignments
\setbox0=\vtop{\unvcopy#1}% %% <- wrap in a vtop
\setbox2=\vbox{\unvcopy#1}% %% <- wrap in a vbox
\@tempdima=\dimexpr\dp0-\dp2\relax %% <- calculate missing depth
\dp0=\dp2 %% <- use depth of box2
\par\box0 %% <- use the vtop
\vspace*{\@tempdima}% %% <- insert missing depth as space
\endgroup
}
\makeatother
This command should not be preceded by \noindent
(which switches to horizontal mode) because \insertvbox
was intended to insert this box in vertical mode. (The \par
in its definition actually switches back to vertical mode.)
If it is for some reason important that the box be inserted in horizontal mode (perhaps because you want a non-zero \parskip
to be inserted), then you can replace \par\box0
by \par\noindent\box0
in the definition.
(I'm afraid I don't know why the lineno
package does not like the version with \noindent
though.)
Example
The following example produces two identical pages: the first was constructed with vertical boxes, and the second without.
\documentclass{article}
\usepackage{showframe} %% <- show boundary of text area
\usepackage{tikz} %% <- To draw the red brackets
\usepackage{tikzpagenodes} %% <- to place them correctly
\makeatletter
\newcommand*\insertvbox[1]{%
\begingroup
\setbox0=\vtop{\unvcopy#1}% %% <- box1 is a vtop
\setbox2=\vbox{\unvcopy#1}% %% <- box2 is a vbox
\@tempdima=\dimexpr\dp0-\dp2\relax %% <- calculate missing depth
\dp0=\dp2 %% <- use depth of box2
\par\box0 %% <- use the vtop
\vspace*{\@tempdima}% %% <- insert missing depth as space
\endgroup
}
\makeatother
\parskip=0pt %% <- default value is "0pt plus 1pt"
\newcommand\redbracket{% %% <- for the little red brackets in the left margin
\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-\X\baselineskip]current page text area.north west) -- +(-2pt,0)
-- node[left]{\texttt{\detokenize{\baselineskip}}} +(-2pt,-\baselineskip) -- +(0,-\baselineskip);
}
\end{tikzpicture}%
}
\begin{document}
\newsavebox{\TitleBox}
\newsavebox{\BodyBox}
\setbox\TitleBox=\vtop{\noindent\textbf{A title that takes\\up more than\\two lines}}
\setbox\BodyBox=\vtop{\noindent Lorem\\ipsum}
\insertvbox\TitleBox
\insertvbox\BodyBox
\insertvbox\BodyBox
\insertvbox\BodyBox
\redbracket
\clearpage
\noindent\textbf{A title that takes\\up more than\\two lines}
\noindent Lorem\\ipsum \par\noindent Lorem\\ipsum \par\noindent Lorem\\ipsum
\redbracket
\end{document}
Note that I've set \parskip=0pt
because the default value of \parskip
is actually 0pt plus 1pt
.
This means that the amount of space between paragraphs could be stretched to up to 1pt
to improve the page layout.
In your MWE
The spacing in your MWE will be correct if you replace \noindent\usebox
by \insertvbox
(and add the above definition to your preamble, of course):
\def\UseBoxes{}% 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#1\vtop{%
\bfseries\centering%
#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#1\vtop{%
\noindent#2%
}%
}%
\makeatletter
\newcommand*\insertvbox[1]{%
\begingroup
\setbox0=\vtop{\unvcopy#1}% %% <- wrap in a vtop
\setbox2=\vbox{\unvcopy#1}% %% <- wrap in a vbox
\@tempdima=\dimexpr\dp0-\dp2\relax %% <- calculate missing depth
\dp0=\dp2 %% <- use depth of box2
\par\box0 %% <- use the vtop
\vspace*{\@tempdima}% %% <- insert missing depth as space
\endgroup
}
\makeatother
\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} {%
\ifnum\X>\NumberOfTitleLines
%% After title: need to adjust \AdditionalSkip for space after title
\setlength{\AdditionalSkip}{%
\dimexpr\DesiredSkipAboveTitle+\DesiredSkipBelowTitle\relax%
}%
\fi
\draw [thin, red] ([yshift=-\X\baselineskip-\AdditionalSkip]X) -- ++ (\hsize,0);
}%
\tikzset{Node Style/.style={anchor=south, draw=red, inner sep=1pt}}
\ifdefined\UseBoxes
\node [Node Style, fill=yellow] at ([xshift=0.5\hsize]X) {using boxes};
\else
\node [Node Style, fill=green] at ([xshift=0.5\hsize]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}%
%%
\ifdefined\UseBoxes
\insertvbox{\TitleBox}%
\vspace*{\DesiredSkipBelowTitle}%
\insertvbox{\BodyBox}%
\else
\noindent{\bfseries\centering\Title\par}%
\vspace*{\DesiredSkipBelowTitle}%
\par\noindent\SelectedBodyText%
\fi
\noindent\ShowTextGuideLines{7}%
\end{document}
Note that if you want the distance between consecutive lines to be exactly \baselineskip
even if this would cause these lines to overlap, you can set \lineskiplimit=-\maxdimen
(either inside your \vbox
es or globally).
Old solution
The macro \copyboxwithappropriatespace
, which I define below (and which could use a better name), does roughly the same thing as \insertvbox
. It however makes some assumptions to calculate the right depth to use and the right space to insert. It assumes that (1) it is provided a \vtop
, (2) that the distance between the first and the last baseline inside the box is an integer multiple of \baselinekip
and (3) that the depth of the last line is less than \baselineskip
.
This means that the amount of space to be inserted is equal to greatest integer multiple of \baselineskip
that is less than the depth of your box, and that that the depth of the box should be equal to the remainder (as suggested by jfbu in the comments).
\makeatletter %% <-- make @ usable in command sequences
%% Inserts a box of depth 0 and insert an appropriate vspace:
\newcommand*\copyboxwithappropriatespace[1]{%
\begingroup
\@tempdima=\dp#1\relax %% <-- store the depth in \@tempdima
\count@=\@tempdima\relax %% <-- convert to a number
\divide\count@ by \baselineskip\relax %% <-- divide by \baselineskip
\dp#1=\dimexpr\@tempdima-\count@\baselineskip\relax %% <-- set the depth to to the remainder
\par\copy#1% %% <-- insert the box
\dp#1=\@tempdima\relax %% <-- reset its depth
\vspace*{\count@\baselineskip}%
\endgroup %% ^-- insert \count@ \baselineskips
}
\makeatother %% <-- revert @
Best Answer
I am just guessing here, but I believe box dimensions are associated with each box. You don't have special dimen registers for these. This is what I believe happens here. After you enter the first group, at the moment you assign \setbox0\hbox{ZZZ}, TeX assigns a local box register. You then enter the second group, but your box register 0 is still the same \hbox{ZZZ}, TeX will not create a local copy of the box. So when you assign 0pt to \wd0, you modify the \hbox{ZZZ} from the previous group.
Try to modify your code like this:
and see what happens.
Edit: I think the following behavior is related to this:
Notice that the \box0 inside the group empties the box register, it does not get restored at the end of the group.