[Tex/LaTex] Non-invasive replacement for \fbox

boxesspacing

I am trying to create a macro that improves on \fbox by working correctly in all cases and by being minimally invasive with respect to padding. What I've got so far works fairly well, but I'm having trouble measuring text width, height, and depth in certain cases. My question is How do I calculate these correctly?

Why I don't like \fbox:

  1. Notice below how \fbox gets the bounding boxes correct on the ‘,’ and the ‘l’, but completely messes up the fraction size when invoked as $\frac{\fbox{1}}{\fbox{2}}$ and the superscript size when invoked as $x^{\fbox{2}}$.

  2. \fbox draws a rule around the bounding box rather than inside it. Thus, no matter how thin the rule, it still alters the dimensions of the text being boxed. In other words, \fbox is not minimally invasive. Below, the padding and line thickness are set with \setlength\fboxsep{0pt} and \setlength\fboxrule{0.001pt}. Setting \fboxrule to 0pt causes the rule to disappear altogether.

\fbox hello world

First attempt at a non-invasive box (using \vrule):

My first attempt used a \vrule. This was good, but it obscured text when the bounding box overlapped another character, for example the ‘r’ below. Worse yet, I didn't see how I could draw a dark rule around the box. So I threw out that idea.

\vrule hello world

Second attempt (using TikZ):

Next I used TikZ to draw a translucent filled box. The edge is not a stroked path around the box but rather is four very thin filled rectangles inside the bounding box.

\fillbox hello world

Now this is getting somewhere. The problem, however, is in how I’m measuring the width, height, and depth of the bounding box. I'm using \settowidth, \settoheight, and \settodepth and giving these the value #1 passed to the macro. This approach works great for regular text, but once something is in a superscript, subscript, a fraction, or a stacked relation—or in any other font or size for any reason—it breaks down.

Problems:

As you can see, the widths, heights, and depths of certain elements below have been calculated incorrectly. The most egregious of is the \cdot, whose bounding box appears to its left because my macro doesn't know that it is a binary relational operator needing extra space. I'm not sure if that is fixable, but is there at least some way to fix the superscripts and subscripts? Because although $v_{\fillbox{x}}$ comes out with undisturbed spacing, it's measuring the size of x’s bounding box based on #1 being x (normal size and style) and not _x (subscripted math size and style).

\fillbox code

Finally, why does the stacked relation disturb the horizontal spacing?

Minimal working example:

\documentclass{article}

\usepackage{tikz}

\makeatletter
\newlength\fillbox@height%
\newlength\fillbox@depth%
\newlength\fillbox@width%
\newcommand{\fillbox}[1]{%
  \ifmmode%
    \settoheight{\fillbox@height}{$#1$}%
    \settodepth{\fillbox@depth}{$#1$}%
    \settowidth{\fillbox@width}{$#1$}%
  \else%
    \settoheight{\fillbox@height}{#1}%
    \settodepth{\fillbox@depth}{#1}%
    \settowidth{\fillbox@width}{#1}%
  \fi%
  \raisebox{-\fillbox@depth}{%
    \begin{tikzpicture}[scale=1]
    \pgfsetfillcolor{yellow!90!red}
    \pgfsetfillopacity{0.5}
    \pgfsetrectcap
    \fill (0,-\fillbox@depth) rectangle (\fillbox@width,\fillbox@height);
    \pgfsetfillcolor{yellow!50!black}
    \pgfsetfillopacity{1}
    \fill (0,-\fillbox@depth)
          rectangle (\fillbox@width,-\fillbox@depth+.1pt);
    \fill (0,\fillbox@height)
          rectangle (\fillbox@width,\fillbox@height-.1pt);
    \fill (0,-\fillbox@depth)
          rectangle (.1pt,\fillbox@height);
    \fill (\fillbox@width,-\fillbox@depth)
          rectangle (\fillbox@width-.1pt,\fillbox@height);
    \end{tikzpicture}%
    \kern-\fillbox@width%
  }%
  #1%
}
\makeatother

\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}

(Note: I didn't tag this question with tikz because even though it uses TikZ, the question is really about spacing and boxing; TikZ just happens to be used.)

Best Answer

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.

enter image description here

\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

enter image description here