[Tex/LaTex] How to lower the \widetilde accent, i.e., move it closer to the variable

accentsmath-modepositioningsymbols

I sometimes find that the \widetilde accents sits too high above the math letter it is applied to. An example:

To me it looks as if the first tilde is sitting just in between the two lines; it is not visually attached to the "B". As the second "B tilde" shows, I did manage to produce a version where the \widetilde is lowered, but this is an ugly hack using \textcolor:

\rlap{\raisebox{-0.1ex}{$\widetilde{B}$}}
\rlap{\smash{\textcolor{white}{\rule[-1ex]{2em}{2.7ex}}}}
B

What I'm doing here: I lower the whole \widetilde{B}, then I erase the "B" by some (manually adjusted) white box, and finally I typeset the "B" that is seen in the output. Of course I have this wrapped in some macro, but this macro does not work for small letters such as "m", which is due to the white box.

The more straightforward attempt

\rlap{\raisebox{-0.1ex}{$\widetilde{\phantom{B}}$}}B

does not work as the output shows: the tilde in is too far to the left. The reason for this is that TeX handles accents over single characters differently, using the \skewchar of the font, and \phantom{B} is not a single character. Moreover, in the version \tilde B I find the tilde too small: .

One of the problems of my solution is that it doesn't render properly when I view the dvi file in Yap. Does anyone know a better solution to lower the \widetilde?

EDIT:

TH's first solution works rather well. It has the only drawback that it does not lower the \widetilde over small letters such as "m". However, this is not a big deal since in this case the tilde does not look as if it is sitting in between two lines. I came up with an answer that does lower the \widetilde over "m", but that has other major problems. TH's (rather involved) second solution fixes most of these problems. Great work!

Best Answer

Implementation 1
This code changes the x-height of the accent font for its argument. It should be fairly robust. Rather than scaling the height by 1.3, one could simply add a fixed value to it. Because of the way TeX computes accent heights, there is some minimum height of an accent and so this tends to have no discernible effect on lowercase letters.

\makeatletter
\newcommand*\wt[1]{\mathpalette\wthelper{#1}}
\newcommand*\wthelper[2]{%
        \hbox{\dimen@\accentfontxheight#1%
                \accentfontxheight#11.3\dimen@
                $\m@th#1\widetilde{#2}$%
                \accentfontxheight#1\dimen@
        }%
}

\newcommand*\accentfontxheight[1]{%
        \fontdimen5\ifx#1\displaystyle
                \textfont
        \else\ifx#1\textstyle
                \textfont
        \else\ifx#1\scriptstyle
                \scriptfont
        \else
                \scriptscriptfont
        \fi\fi\fi3
}
\makeatother

Implementation 2
This attempts to duplicate Rule 12 and the relevant portions of Rules 16–18 in Appendix G of The TeXbook for typesetting math accents.

Some notes on the implementation: I don't know how to compute successors, so instead I replace box y which should just be the accent and the italic correction with $\widetilde{\hphantom{...}}$. As a result, the tilde should already be shifted by half(w+width(y)). From tex.web, the shift amount of box y is given by
shift_amount(y):=s+half(w-width(y));
thus it suffices to compute the kerning s and shift right by that amount.

The code below contains 3 main parts.

  1. The first thing it does is tries to parse the argument to \wt which will appear as the nucleus of the Acc atom. This is extremely brittle. If the argument expands to any primitives other than \relax, \bgroup, \egroup, \begingroup, \endgroup, \fam, (control space), \char (not tested), \mathchar (not tested), \mathcode (not tested), a letter, or a symbol, it will treat the nucleus as not a single character even if it really is. If there is an unknown control sequence, for example \let\unknown\foo, it will fail with a mysterious error because it cannot expand \unknown. Some effort is put into checking for changes of fonts.

    If the \wt@checknucleus macro decides that the nucleus of the Acc atom we're about to fake is just a single character, it stores the \mathcode and notes the fact by setting \ifwt@nucleussingle to \iftrue. (I suppose the third part of the code could just check the saved math code instead.)

    • After \wt@checknucleus is complete, it scans ahead looking for _ and ^. If these are hidden in macros, it won't find them! Any subscripts and superscripts are saved for later processing in either Rule 12 or Rule 18.

    • Finally, \wt@choice uses \mathchoice to typeset the formula in each of the 4 styles which is done by \wt@applyrules and then pick the appropriate one. \wt@applyrules contains the relevant rules from The TeXbook as comments followed by some code that attempts to apply the rules.

The code is not perfect. See the output from my test at the bottom of this answer. Also, it loses crampedness because pdfTeX provides no way to test for it. It does perform the appropriate cramping where Rule 12 calls for it.

\makeatletter
\newif\ifwt@nucleussingle
\newif\ifwt@sub
\newif\ifwt@sup
\newtoks\wt@nucleus
\newtoks\wt@sub
\newtoks\wt@sup
\newcount\wt@nucleusmathcode
\newcount\wt@fam
\newdimen\wt@s
\newdimen\wt@delta

\DeclareRobustCommand*\wt[1]{%
        \begingroup
        \wt@nucleus{#1}%
        \wt@checknucleus%
        \wt@subfalse
        \wt@supfalse
        \futurelet\wt@temp\wt@parse
}

\def\wt@checknucleus{%
        \begingroup
        \wt@nucleussinglefalse
        \setbox\z@\hbox{$\the\wt@nucleus
                \def\use@mathgroup##1##2{\relax\math@bgroup
                        \mathgroup##2\relax\math@egroup}%
                \expandafter\xdef\expandafter\wt@gtemp
                        \expandafter{\the\wt@nucleus}$}%
        \expandafter\wt@checktoken\wt@gtemp\wt@sentinel
        \endgroup
        \wt@gtemp
}

\def\wt@sentinel{\wt@sentinel}

\def\wt@checktoken#1{%
        \let\wt@temp\wt@checktoken
        \ifx#1\relax
        \else\ifx#1\bgroup
                \bgroup
        \else\ifx#1\egroup
                \egroup
        \else\ifx#1\begingroup
                \begingroup
        \else\ifx#1\endgroup
                \endgroup
        \else\ifx#1\fam
                \let\wt@temp\fam
                \afterassignment\wt@checktoken
        \else\ifx#1            \else\ifx#1\wt@sentinel
                \let\wt@temp\relax
        \else\ifx#1\@sptoken
        \else\ifcat#1a%
                \wt@checksingle{\mathcode`#1}%
        \else\ifcat#1/%
                \wt@checksingle{\mathcode`#1}%
        \else\ifx#1\char
                \let\wt@temp\count@
                \afterassignment\wt@checkchar
        \else\ifx#1\mathchar
                \let\wt@temp\count@
                \afterassignment\wt@checkmathchar
        \else\ifx#1\mathcode
                \let\wt@temp\mathcode
                \afterassignment\wt@checktoken
        \else
                \global\let\wt@gtemp\wt@nucleussinglefalse
                \wt@nucleussingletrue % Not really true...
        % In principle, this could handle every primitive.
        \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi
        \wt@temp
}

\def\wt@checkchar{%
        \wt@checksingle{\mathcode\count@}%
        \wt@checktoken
}

\def\wt@checkmathchar{%
        \wt@checksingle{\count@}%
        \wt@checktoken
}

\def\wt@checksingle#1{%
        \ifwt@nucleussingle
                \global\let\wt@gtemp\wt@nucleussinglefalse
        \else
                \xdef\wt@gtemp{%
                        \noexpand\wt@nucleussingletrue
                        \wt@fam\the\fam\relax
                        \wt@nucleusmathcode\the#1\relax
                }%
                \wt@nucleussingletrue
        \fi
}

\def\wt@parse{%
        \let\wt@next\wt@choice
        \ifdefmacro{\wt@temp}{}%
        {%
                \ifcat_\wt@temp
                        \ifwt@sub\else
                                \let\wt@next\wt@parsesub
                        \fi
                \else\ifcat^\wt@temp
                        \ifwt@sup\else
                                \let\wt@next\wt@parsesup
                        \fi
                \else\ifcat\@sptoken\wt@temp
                        \def\wt@next##1{\futurelet\wt@temp\wt@parse}%
                \fi\fi\fi
        }%
        \wt@next
}

\def\wt@parsesub#1#2{%
        \wt@sub{#2}%
        \wt@subtrue
        \futurelet\wt@temp\wt@parse
}

\def\wt@parsesup#1#2{%
        \wt@sup{#2}%
        \wt@suptrue
        \futurelet\wt@temp\wt@parse
}


\def\wt@choice{%
        \mathchoice{\wt@applyrules\displaystyle\textfont}%
                   {\wt@applyrules\textstyle\textfont}%
                   {\wt@applyrules\scriptstyle\scriptfont}%
                   {\wt@applyrules\scriptscriptstyle\scriptscriptfont}%
        \endgroup
}

\def\wt@applyrules#1#2{%
        % The following block comments are quotes from Knuth's The
        % TeXbook. My notes appear [in brackets].
        %
        % Rule 12:
        % If the current item is an Acc atom (from \mathaccent) [this
        % is what we're constructing], just go to Rule 16 if the
        % accent character doesn't exist in the current size [ignoring
        % this]. Otherwise set box x to the nucleus in style C', and
        % set u to the width of this box.
        % [We don't need u.]
        \setbox\z@\hbox{$\m@th\cramped[#1]{\the\wt@nucleus}$}%
        % If the nucleus is not a single character, let s = 0;
        % otherwise set s to the kern amount for the nucleus followed
        % by the \skewchar of its font
        \ifwt@nucleussingle
                \count@\wt@nucleusmathcode
                \divide\count@\@cclvi
                \@tempcnta\count@
                \divide\@tempcnta8
                \multiply\@tempcnta8
                \advance\count@-\@tempcnta
                \ifnum\@tempcnta="70
                        \ifnum\wt@fam>\z@
                                \ifnum\wt@fam<16
                                        \count@\wt@fam
                                \fi
                        \fi
                \fi
                \@tempcnta\wt@nucleusmathcode
                \divide\@tempcnta\@cclvi
                \multiply\@tempcnta\@cclvi
                \advance\@tempcnta-\wt@nucleusmathcode
                \@tempcnta-\@tempcnta
                \edef\currentfont{\the#2\count@}%
                \count@\skewchar\currentfont
                \ifnum\count@=\m@ne
                        \wt@s\z@ % s
                \else
                        \setbox\tw@\hbox{\currentfont\char\@tempcnta\char\count@}%
                        \wt@s\wd\tw@
                        \setbox\tw@\hbox{\currentfont\char\@tempcnta}%
                        \advance\wt@s-\wd\tw@
                        \setbox\tw@\hbox{\currentfont\char\count@}%
                        \advance\wt@s-\wd\tw@ % s
                \fi
        \else
                \wt@s\z@
        \fi
        % If the accent character has a successor in its font whose
        % width is <= u, change it to the successor and repeat this
        % sentence.
        % [I don't know how to do this, so I'm ignoring it.]
        % 
        % Now set delta <- min(h(x), chi), where chi is \fontdimen5
        % (the x-height in the accent font).
        \edef\accentfont{\the#2\thr@@}%
        \wt@delta\fontdimen5\accentfont % x-height in accent font
        \ifdim\wt@delta>\ht\z@ \wt@delta\ht\z@ \fi % delta
        \advance\wt@delta\p@ % Increase delta by 1pt
        % If the nucleus is a single character, replace box x by a box
        % containing the nucleus together with the superscript and
        % subscript of the Acc atom, in style C, and make the
        % sub/superscript of the Acc atom empty; also increase delta
        % by the difference between the new and old values of h(x).
        % [Note that we lose crampedness here since one cannot check
        % for it with pdfTeX.]
        \ifwt@nucleussingle
                \advance\wt@delta-\ht\z@
                \setbox\z@\hbox{$\m@th#1%
                        \the\wt@nucleus
                        \ifwt@sub_{\the\wt@sub}\fi
                        \ifwt@sup^{\the\wt@sup}\fi $}%
                \wt@subfalse
                \wt@supfalse
                \advance\wt@delta\ht\z@
        \fi
        % Put the accent into a new box y, including the italic
        % correction.
        % [\setbox\tw@\hbox{\accentfont\char"65\/} would work except
        % that I didn't find the larger accents above. Instead, use
        % \widetilde and \hphantom.]
        \setbox\tw@\hbox{$\m@th#1\widetilde{\hphantom{\the\wt@nucleus}}$}% box y
        \wd\tw@\z@
        % Let z be a vbox consisting of: boy y moved right s +
        % (1/2)(u-w(y)), kern -delta, and box x.
        \setbox\tw@\vbox{%
                % [Since we aren't setting the accent ourself, we only
                % need to move right by s since the \widetilde will
                % take care of the rest.
                % \moveright\dimexpr\dimen@+.5\wd\z@-.5\wd\tw@\relax\box\tw@]
                \moveright\wt@s\box\tw@
                \nointerlineskip
                \kern-\wt@delta
                \copy\z@
        }% box z
        % If h(z) < h(x), add a kern of h(x) - h(z) above box y and
        % set h(z) <- h(x).
        \ifdim\ht\tw@<\ht\z@
                \dimen@\ht\z@
                \advance\dimen@\ht\tw@
                \setbox\tw@\vbox{%
                        \kern\dimen@
                        \unvbox\tw@
                }%
                \ht\tw@\ht\z@
        \fi
        % Finally set w(z) <- w(x),
        \wd\tw@\wd\z@
        % replace the nucleus of the Acc atom by box z, and continue
        % with Rule 16.
        %
        % Rule 16:
        % Change the current item to an Ord atom, and continue with
        % Rule 17.
        \mathord{\box\tw@}%
        %
        % Rule 17:
        % If the nucleus of the current item is a math list [it
        % isn't]...
        % Then if the nucleus is not simply a symbol [it isn't], go on
        % to Rule 18. ...
        %
        % Rule 18:
        % (The remaining task for the current atom is to attach a
        % possible subscript and superscript.) If both the subscript
        % and superscript fields are empty, move to the next item.
        % Otherwise continue with the following subrules.
        % [Let's let TeX deal with subscripts and superscripts here.]
        \ifwt@sub_\the\wt@sub\fi
        \ifwt@sup^\the\wt@sup\fi
}
\makeatother

This requires the mathtools package for \cramped and etoolbox for \ifdefmacro.

This is my current test. Note that \mathrm doesn't work properly for some reason.

$\widetilde{B}\wt{B}$
$\widetilde{m}\wt{m}$
$\widetilde{W}\wt{W}$
$\widetilde{w}\wt{w}$
$\widetilde{XY}\wt{XY}$

$\widetilde{\mathcal{B}}\wt{\mathcal{B}}$
$\widetilde{\mathcal{M}}\wt{\mathcal{M}}$
$\widetilde{\mathcal{W}}\wt{\mathcal{W}}$
$\widetilde{\mathcal{I}}\wt{\mathcal{I}}$
$\widetilde{\mathcal{XY}}\wt{\mathcal{XY}}$

$\widetilde{\mathrm{B}}\wt{\mathrm{B}}$
$\widetilde{\mathrm{M}}\wt{\mathrm{M}}$
$\widetilde{\mathrm{W}}\wt{\mathrm{W}}$
$\widetilde{\mathrm{I}}\wt{\mathrm{I}}$
$\widetilde{\mathrm{XY}}\wt{\mathrm{XY}}$

\newcommand\triple[2]{#1{#2}_{#1{#2}_{#1{#2}}}}
\newcommand\tw{\triple\widetilde}
\newcommand\twt{\triple\wt}
$\tw A\tw B\tw I\tw W\tw m\tw w$

$\twt A\twt B\twt I\twt W\twt m\twt w$