28

In the third "dangerous bend" on page 178 of of The TeXbook there's the following description of \vphantom:

Even more useful than \phantom is \vphantom, which makes an invisible box whose height and depth are the same as those of the corresponding \phantom, but the width is zero.

In Appendix B (page 360) one finds the definitions for \vphantom, \hphantom and \phantom:

\newif\ifv@ \newif\ifh@
\def\vphantom{\v@true\h@false\ph@nt}
\def\hphantom{\v@false\h@true\ph@nt}
\def\phantom{\v@true\h@true\ph@nt}
\def\ph@nt{\ifmmode\def\next{\mathpalette\mathph@nt}%
  \else\let\next=\makeph@nt\fi \next}
\def\makeph@nt#1{\setbox0=\hbox{#1}\finph@nt}
\def\mathph@nt#1#2{\setbox0=\hbox{$\m@th#1{#2}$}\finph@nt}
\def\finph@nt{\setbox2=\null \ifv@ \ht2=\ht0 \dp2=\dp0 \fi
  \ifh@ \wd2=\wd0 \fi \box2 }

And here are the definitions in the LaTeX kernel (ltmath.dtx, page 256 of source2e.pdf):

\newif\ifv@
\newif\ifh@
\def\vphantom{\v@true\h@false\ph@nt}
\def\hphantom{\v@false\h@true\ph@nt}
\def\phantom{\v@true\h@true\ph@nt}
\def\ph@nt{%
  \ifmmode
    \expandafter\mathpalette\expandafter\mathph@nt
  \else
    \expandafter\makeph@nt
  \fi}
\def\makeph@nt#1{%
  \setbox\z@\hbox{\color@begingroup#1\color@endgroup}\finph@nt}
\def\mathph@nt#1#2{%
  \setbox\z@\hbox{$\m@th#1{#2}$}\finph@nt}
\def\finph@nt{%
  \setbox\tw@\null
  \ifv@ \ht\tw@\ht\z@ \dp\tw@\dp\z@\fi
  \ifh@ \wd\tw@\wd\z@\fi \box\tw@}

Since \vphantom preserves total height (=height+depth), I wonder why there's no depth-only and height-only phantoms? More precisely, how can the above definitions be extended to define \dphantom (for depth only) and \htphantom (for height only)? The former would produce an invisible box whose depth is the same as that of the corresponding \phantom but whose width and height are zero, and the latter would produce an invisible box whose height is the same as that of the corresponding \phantom but its width and depth are zero.

3 Answers 3

22

For example, the switch \ifv@ can be split into two switches for the height and depth: \ifv@ht@ and \ifv@dp@.

The the macros are changed in this way (unchanged macros are commented):

plain TeX

% \newif\ifh@            
\newif\ifv@ht@ \newif\ifv@dp@
\def\vphantom {\v@ht@true \v@dp@true \h@false\ph@nt}
\def\hphantom {\v@ht@false\v@dp@false\h@true \ph@nt}
\def\htphantom{\v@ht@true \v@dp@false\h@false\ph@nt}  
\def\dpphantom{\v@ht@false\v@dp@true \h@false\ph@nt}  
\def\phantom  {\v@ht@true \v@dp@true \h@true \ph@nt}      
%\def\ph@nt{\ifmmode\def\next{\mathpalette\mathph@nt}%
%  \else\let\next=\makeph@nt\fi \next}
%\def\makeph@nt#1{\setbox0=\hbox{#1}\finph@nt}
%\def\mathph@nt#1#2{\setbox0=\hbox{$\m@th#1{#2}$}\finph@nt}
\def\finph@nt{%
  \setbox2=\null
  \ifv@ht@ \ht2=\ht0 \fi
  \ifv@dp@ \dp2=\dp0 \fi  
  \ifh@ \wd2=\wd0 \fi   
  \box2 %
}

% Macro \test, which visualizes the bounding box
\def\test#1#2{%
  \leavevmode
  \hbox{%  
    \vrule  
    \vbox{%  
      \hrule 
      \vtop{%
        \hbox{#1{#2}}%
        \hrule
      }%
    }%
    \vrule
  }%
}

g \test\phantom{g} \test\hphantom{g} \test\vphantom{g}
\test\htphantom{g} \test\dpphantom{g}
\bye

Test result

LaTeX

LaTeX has inherited these macros from plain TeX. It has only changed \makeph@nt to add color support. But this command does not need to be changed. The following example redefines these macros for LaTeX:

\documentclass{article}

\makeatletter
\newif\ifv@ht@ \newif\ifv@dp@
\renewcommand*{\vphantom} {\v@ht@true \v@dp@true \h@false\ph@nt}
\renewcommand*{\hphantom} {\v@ht@false\v@dp@false\h@true \ph@nt}
\newcommand*  {\htphantom}{\v@ht@true \v@dp@false\h@false\ph@nt}
\newcommand*  {\dpphantom}{\v@ht@false\v@dp@true \h@false\ph@nt}
\renewcommand*{\phantom}  {\v@ht@true \v@dp@true \h@true \ph@nt}
\renewcommand*{\finph@nt}{%
  \setbox2=\null
  \ifv@ht@ \ht2=\ht0 \fi
  \ifv@dp@ \dp2=\dp0 \fi
  \ifh@ \wd2=\wd0 \fi
  \box2 %
}
\makeatother

\begin{document}
  % Macro \test visualizes the bounding box
  \newcommand*{\test}[2]{%
    \begingroup
      \setlength{\fboxsep}{0pt}%
      \fbox{#1{#2}}%
    \endgroup
  }
  g \test\phantom{g} \test\hphantom{g} \test\vphantom{g}
  \test\htphantom{g} \test\dpphantom{g}
\end{document}

Result

17

With amsmath you can define

\newcommand{\heightphantom}[1]{\vphantom{\smash[b]{#1}}
\newcommand{\depthphantom}[1]{\vphantom{\smash[t]{#1}}

Not as efficient as Heiko's, but shorter to define.


An expl3 version, with a key-value interface. The *-version of the command doesn't force horizontal mode, if one really needs it.

The keys specify what dimensions are to be kept; specifying none means keeping all three (height, depth and width). Thus \xphantom{g} is mainly equivalent to \phantom{g} (apart from the automatic \leavevmode), while \xphantom[width]{g} and \xphantom[totalheight]{g} are equivalent to \hphantom and \vphantom respectively. Shorthands \xhphantom and \xvphantom are provided for these.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\xphantom}{som}
 {% #2 is a list of key-value options, #3 is the text
  \IfBooleanF{#1}{\leavevmode}
  \IfNoValueTF{#2}
   {
    \manual_phantom:nn { totalheight , width } { #3 }
   }
   {
    \manual_phantom:nn { #2 } { #3 }
   }
 }
\NewDocumentCommand{\xhphantom}{sm}
 {
  \IfBooleanTF{#1}
   {
    \xphantom*[width]{#2}
   }
   {
    \xphantom[width]{#2}
   }
 }
\NewDocumentCommand{\xvphantom}{sm}
 {
  \IfBooleanTF{#1}
   {
    \xphantom*[totalheight]{#2}
   }
   {
    \xphantom[totalheight]{#2}
   }
 }

\keys_define:nn { manual/xphantom }
 {
  height .bool_set:N  = \l__manual_phantom_keepht_bool,
  depth  .bool_set:N  = \l__manual_phantom_keepdp_bool,
  width  .bool_set:N  = \l__manual_phantom_keepwd_bool,
  totalheight .code:n = 
    \bool_set_true:N \l__manual_phantom_keepht_bool
    \bool_set_true:N \l__manual_phantom_keepdp_bool,
  height .default:n = true,
  depth  .default:n = true,
  width  .default:n = true,
  totalheight .default:n = ,
 }

\box_new:N \l__manual_phantom_input_box
\box_new:N \l__manual_phantom_box

\cs_new_protected:Npn \manual_phantom:nn #1 #2
 {
  \group_begin:
  \keys_set:nn { manual/xphantom } { #1 }
  \hbox_set:Nn \l__manual_phantom_input_box { #2 }
  \hbox_set:Nn \l__manual_phantom_box {}
  \bool_if:NT \l__manual_phantom_keepht_bool
   {
    \box_set_ht:Nn \l__manual_phantom_box { \box_ht:N \l__manual_phantom_input_box }
   }
  \bool_if:NT \l__manual_phantom_keepdp_bool
   {
    \box_set_dp:Nn \l__manual_phantom_box { \box_dp:N \l__manual_phantom_input_box }
   }
  \bool_if:NT \l__manual_phantom_keepwd_bool
   {
    \box_set_wd:Nn \l__manual_phantom_box { \box_wd:N \l__manual_phantom_input_box }
   }
  \box_use:N \l__manual_phantom_box
  \group_end:
 }
\ExplSyntaxOff

\begin{document}

\newcommand*{\test}[1]{%
  \begingroup
    \setlength{\fboxsep}{0pt}%
    \fbox{#1}%
  \endgroup
}

g
\test{\xphantom{g}}
\test{\xphantom[width]{g}}
\test{\xphantom[totalheight]{g}}
\test{\xphantom[height]{g}}
\test{\xphantom[depth]{g}}

g
\test{\xphantom[height,width]{g}}
\test{\xphantom[depth,width]{g}}

g
\test{\xhphantom{g}}
\test{\xvphantom{g}}

\end{document}

enter image description here


A version also implementing math mode:

\documentclass{article}
\usepackage{amsmath,xparse}

\ExplSyntaxOn
\NewDocumentCommand{\xphantom}{som}
 {% #2 is a list of key-value options, #3 is the text
  \IfBooleanF{#1}{ \mode_if_math:F { \leavevmode } }
  \IfNoValueTF{#2}
   {
    \manual_phantom:nn { totalheight , width } { #3 }
   }
   {
    \manual_phantom:nn { #2 } { #3 }
   }
 }
\NewDocumentCommand{\xhphantom}{sm}
 {
  \IfBooleanTF{#1}
   {
    \xphantom*[width]{#2}
   }
   {
    \xphantom[width]{#2}
   }
 }
\NewDocumentCommand{\xvphantom}{sm}
 {
  \IfBooleanTF{#1}
   {
    \xphantom*[totalheight]{#2}
   }
   {
    \xphantom[totalheight]{#2}
   }
 }

\keys_define:nn { manual/xphantom }
 {
  height .bool_set:N  = \l__manual_phantom_keepht_bool,
  depth  .bool_set:N  = \l__manual_phantom_keepdp_bool,
  width  .bool_set:N  = \l__manual_phantom_keepwd_bool,
  totalheight .code:n = 
    \bool_set_true:N \l__manual_phantom_keepht_bool
    \bool_set_true:N \l__manual_phantom_keepdp_bool,
  height .default:n = true,
  depth  .default:n = true,
  width  .default:n = true,
  totalheight .default:n = ,
 }

\box_new:N \l__manual_phantom_input_box
\box_new:N \l__manual_phantom_box

\cs_new_protected:Npn \manual_phantom:nn #1 #2
 {
  \group_begin:
  \keys_set:nn { manual/xphantom } { #1 }
  \hbox_set:Nn \l__manual_phantom_box {}
  \mode_if_math:TF
    {
     \__manual_phantom_math:n { #2 }
    }
    {
     \hbox_set:Nn \l__manual_phantom_input_box { #2 }
     \__manual_phantom_set:
    }
  \group_end:
 }
\cs_new_protected:Npn \__manual_phantom_set:
 {
  \bool_if:NT \l__manual_phantom_keepht_bool
   {
    \box_set_ht:Nn \l__manual_phantom_box { \box_ht:N \l__manual_phantom_input_box }
   }
  \bool_if:NT \l__manual_phantom_keepdp_bool
   {
    \box_set_dp:Nn \l__manual_phantom_box { \box_dp:N \l__manual_phantom_input_box }
   }
  \bool_if:NT \l__manual_phantom_keepwd_bool
   {
    \box_set_wd:Nn \l__manual_phantom_box { \box_wd:N \l__manual_phantom_input_box }
   }
  \box_use:N \l__manual_phantom_box
 }

% Now math mode; there's currently no syntactic sugar for \mathpalette
\cs_new_protected:Npn \__manual_phantom_math:n #1
 {
  \mathpalette \__manual_make_phantom_math:nn { #1 }
 }
\cs_new_protected:Npn \__manual_make_phantom_math:nn #1 #2
 {
  \hbox_set:Nn \l__manual_phantom_input_box { $ #1 {#2} \mathsurround=0pt $ }
  \__manual_phantom_set:
 }
\ExplSyntaxOff

\begin{document}

\newcommand*{\test}[1]{%
  \begingroup
    \setlength{\fboxsep}{0pt}%
    \fbox{#1}%
  \endgroup
}

g
\test{\xphantom{g}}
\test{\xphantom[width]{g}}
\test{\xphantom[totalheight]{g}}
\test{\xphantom[height]{g}}
\test{\xphantom[depth]{g}}

g
\test{\xphantom[height,width]{g}}
\test{\xphantom[depth,width]{g}}

g
\test{\xhphantom{g}}
\test{\xvphantom{g}}

$\displaystyle\sum$
\test{$\displaystyle\xphantom{\sum}$}
\test{$\displaystyle\xvphantom{\sum}$}
\test{$\displaystyle\xhphantom{\sum}$}
\test{$\displaystyle\xphantom[height]{\sum}$}
\test{$\displaystyle\xphantom[depth]{\sum}$}
\test{$\displaystyle\xphantom[height,width]{\sum}$}
\test{$\displaystyle\xphantom[depth,width]{\sum}$}

$\textstyle\sum$
\test{$\textstyle\xphantom{\sum}$}
\test{$\textstyle\xvphantom{\sum}$}
\test{$\textstyle\xhphantom{\sum}$}

$\scriptstyle\sum$
\test{$\scriptstyle\xphantom{\sum}$}
\test{$\scriptstyle\xvphantom{\sum}$}
\test{$\scriptstyle\xhphantom{\sum}$}

$\scriptscriptstyle\sum$
\test{$\scriptscriptstyle\xphantom{\sum}$}
\test{$\scriptscriptstyle\xvphantom{\sum}$}
\test{$\scriptscriptstyle\xhphantom{\sum}$}

\end{document}

enter image description here

3

This doesn't extend the existing \vphantom definition, but provides an alternative. It works in text mode as well as all styles of math mode. EDITED to remove package dependence on scalerel, but the \thisstyle and \savedstyle definitions that remain largely mimic those from the package (\ThisStyle and \SavedStyle). EDITED to add \wdphantom, \htwdphantom, and \dpwdphantom.

\documentclass{article}
\makeatletter
\def\@mstyleD{\displaystyle}
\def\@mstyleT{\textstyle}
\def\@mstyleS{\scriptstyle}
\def\@mstyles{\scriptscriptstyle}
\let\@mstyleX\relax
\def\savedstyle{\csname @mstyle\m@switch\endcsname}
%
\def\thisstyle#1{%
  \ifmmode\mathchoice%
      {\edef\m@switch{D}#1}{\edef\m@switch{T}#1}{\edef\m@switch{S}#1}{\edef\m@switch{s}#1}%
  \else\edef\m@switch{X}#1\fi%
}
\makeatother
\def\preservebox#1{\thisstyle{\setbox0=\hbox{\savedstyle#1}}}
\def\htphantom#1{\preservebox{#1}\rule{0pt}{\ht0}}
\def\dpphantom#1{\preservebox{#1}\rule[-\dp0]{0pt}{\dp0}}
\def\wdphantom#1{\preservebox{#1}\rule{\wd0}{0pt}}
\def\htwdphantom#1{\preservebox{#1}\rule{0pt}{\ht0}\rule{\wd0}{0pt}}
\def\dpwdphantom#1{\preservebox{#1}\rule[-\dp0]{0pt}{\dp0}\rule{\wd0}{0pt}}
\def\test#1{#1 \fbox{\phantom{#1}} \fbox{\vphantom{#1}} 
  \fbox{\wdphantom{#1}} \fbox{\htphantom{#1}} \fbox{\dpphantom{#1}}
    \fbox{\htwdphantom{#1}} \fbox{\dpwdphantom{#1}} \par}
\fboxsep=0pt\relax\fboxrule=.2pt
\begin{document}
\test{g}\test{$g$}\test{$\scriptscriptstyle g$}\test{\Large g}
\test{$\sum_{i=1}^N$}
\test{$\displaystyle\sum_{i=1}^N$}
\end{document}

enter image description here

You must log in to answer this question.

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