19

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!

diabonas
  • 25,784
Hendrik Vogt
  • 37,935
  • You may wish to read http://www.eutypon.gr/eutypon/pdf/e2000-05/e05-a04.pdf (however I couldn't find the package on CTAN). It has very useful information on accents and the parameters to manipulate them. – yannisl Oct 09 '10 at 14:35
  • @Yiannis: I think this won't help since it is about accents in text mode, which are quite different from accents in math mode. – Hendrik Vogt Oct 09 '10 at 15:02
  • fyi, I tracked the accentbx files down and put them up on github here: https://github.com/zellyn/accentbx – zellyn Dec 21 '11 at 20:31
  • @zellyn Thanks for tracking it down and posting it on github. – yannisl Jan 08 '12 at 16:53

2 Answers2

12

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$
TH.
  • 62,639
  • @TH: Both your solutions look very interesting, so +1 for now. Before I accept the answer, I'll have to understand (even) better what you're doing. What I understood so far: Both your solutions have a similar (very good) effect. However, they do not lower the tilde over small letters such as "m", and I think this is due to the minimum of h(x) and \chi in rule 12. Nevertheless, it's already very helpful for my needs. Thanks for your help! – Hendrik Vogt Oct 03 '10 at 20:24
  • @Hendrik Vogt: Yes, I think you're right regarding h(x) and χ. – TH. Oct 03 '10 at 20:28
  • @TH: Looking closer I see that your second solution is no good: Just try it with "W" instead of "B"; there the tilde is much too far to the right. Using the italic correction here is not the right idea (and it is not in the spririt of rule 12). Still the method with the \currentfont is very interesting. – Hendrik Vogt Oct 03 '10 at 20:46
  • @Hendrik Vogt: I'm not sure I agree with your assessment of not being in the spirit of rule 12. It does say, "Put the accent into a new box y, including the italic correction." Unless Knuth means the italic correction of the accent itself, it's doing something with the italic correction of the nucleus. – TH. Oct 03 '10 at 20:54
  • @Hendrik Vogt: I just reread the paragraphs that proceed the list of rules and I think that the italic correction might actually be for the accent. – TH. Oct 03 '10 at 21:11
  • @TH: Yes, I also think it's for the accent. And I deleted my last comment since I made a mistake while testing the code. – Hendrik Vogt Oct 03 '10 at 21:18
  • @TH: I think I now understand the "kern amount for the nucleus followed by the \skewchar of its font". It is the kern amount from the kerning table that would be used between the nucleus and the \skewchar. – Hendrik Vogt Oct 03 '10 at 21:26
  • @Hendrik Vogt: I now have some code that does everything in Rule 12 except get the successor of the accent in the font (and deal with accent sub/superscripts). These are apparently described in the METAFONT manual (which I don't have). As a result, the wide tilde is the smallest one in the font. I'm not even sure if accessing the successors programatically from TeX is possible. – TH. Oct 03 '10 at 23:25
  • @TH: I haven't studied your new second solution yet, but I've tried it, and it doesn't really work either: For "A" it looks perfect, but most of the time the tilde sits too far to the left. Based on your previous second solution, however, I managed to produce something that works; I only have to adapt it so that it also works as expected for small letter such as "m". – Hendrik Vogt Oct 04 '10 at 10:31
  • @Hendrik Vogt: Well, I'm out of ideas. Hopefully someone else will have one. – TH. Oct 04 '10 at 17:59
  • @TH: As you see, I used your ideas for another (suboptimal) answer. I'd rather accept your answer since your first solution is much better. Before I do, could you please remove your second solution, or say more explicitly from the start that there the horizontal positioning still needs to be fixed? One more thing: In your first solution, I think that 1.3\dimen@ is too much; 1.2\dimen@ would be better. – Hendrik Vogt Oct 08 '10 at 08:30
  • @Hendrik Vogt: I think I fixed the problem after reading your suboptimal solution! Take a look at my further edit. I think the correct thing to do would be to uncomment the two commented out lines (and remove one line following each of them), but I don't know how to select the next accent character as described in rule 12. – TH. Oct 08 '10 at 08:49
  • @TH: This looks a lot better, but still not perfect: \wt{\mathcal{M}} still doesn't work properly, probably because of the \ifsinglecharacter. Moreover, the horizontal spacing is changed a bit, as you can see if you try \wt B\wt I. – Hendrik Vogt Oct 08 '10 at 09:15
  • @Hendrik Vogt: You're absolutely right about \mathcal. I'm not sure how to handle that exactly right now. I think adding something like \wt* that means really just treat it as a single character might be the easiest way to do it. I fixed the horizontal spacing by implementing rule 17; i.e., I added a kern for the italic correction. – TH. Oct 08 '10 at 09:58
  • @TH: It's getting better still, great! But the italic correction must only be added if there is no subscript to come, so with my \triple test the horizontal spacing still isn't OK. Sorry to keep pestering you! – Hendrik Vogt Oct 08 '10 at 10:16
  • @Hendrik Vogt: Yeah, I saw that part of rule 17. I was basically going to ignore sub and superscripts since they cannot be handled correctly anyway. In particular, in rule 12, box x gets replaced with the nucleus and sub/superscript of the Acc atom and the sub/superscript of the Acc atom are made empty. Unless there's a way to get at parts of atoms, subscripts working properly is hopeless (unless you want to parse the log file as I mentioned here). – TH. Oct 08 '10 at 10:30
  • @Hendrik Vogt: Okay, that's probably not true. I'll bet one could write something to grab the subscripts and superscripts with \futurelet. Do a \futurelet and then check the catcode. Maybe I'll play with that later. – TH. Oct 08 '10 at 10:44
  • @TH: Wow, I'm rather impressed, even if it still isn't quite perfect. To be honest, I understood only a small portion of your code so far. I'm going to accept your answer now. Still, could you please bring the paragraph starting with "I've got some overly complicated ..." up to date? Most of its contents isn't quite true any more. – Hendrik Vogt Oct 09 '10 at 11:31
  • @Hendrik Vogt: Oh, good catch. This answer has become so unwieldily. I'll fix it up. – TH. Oct 09 '10 at 12:18
  • @TH.: Sorry to pester you again here. First of all: The fixed up answer looks so much better! (Forgot to say thanks for that.) One detail that might interest you: There's no need to define \wt@sentinel in your code since this never gets called. Another thing that keeps nagging me: Somehow the accents package also does all this (but not for \widetilde). Should one try to produce an "upgrade" to that package that offers a \widetilde fix? – Hendrik Vogt Nov 04 '10 at 16:31
  • @Hendrik: It doesn't get expanded, which is good because that would cause an infinite loop, but it does get used in an \ifx test. I do want the two things being compared to be defined and to have the same definition. As for the accents package, if you want to do so, you're more than welcome to use this code, of course. – TH. Nov 05 '10 at 01:15
  • @TH.: Of course it shouldn't get expanded, that would be bad, clear. And OK, maybe it's better to define it, but your code works without it. I didn't find this documented in the TeXbook (nor in TeXbyTopic), but \ifx\1stcs\2ndcs is true if both contol sequences are undefined (assuming that 1 and 2 are letter ...)! As for the package, that'll have to wait, unfortunately, but thanks. – Hendrik Vogt Nov 05 '10 at 10:30
  • @Hendrik: Yeah, I wanted to avoid the possibility that an undefined control sequence was used as an argument which would look like the sentinel to the \ifx test. – TH. Nov 05 '10 at 18:23
2

With the help of TH's various suggestions I came up with the following alternative answer that fixes my first attempt with \phantom. It contains the definition of a macro \wt that takes one optional argument (the lowering amount, defaults to 0.1ex) and one mandatory argument.

\documentclass{article}

\usepackage{amsmath}

\makeatletter
\newcommand*\wt[2][0.1ex]{%
        \begingroup
        \mathchoice{\wt@helper{#1}{#2}{\displaystyle}{\textfont}}
                   {\wt@helper{#1}{#2}{\textstyle}{\textfont}}
                   {\wt@helper{#1}{#2}{\scriptstyle}{\scriptfont}}
                   {\wt@helper{#1}{#2}{\scriptscriptstyle}{\scriptscriptfont}}%
        \endgroup
        #2%
}
\newcommand*\wt@helper[4]{%
        \def\currentfont{\the#41}%
        \def\currentskewchar{\char\the\skewchar\currentfont}%
        \setbox\tw@\hbox{\currentfont#2\currentskewchar}%
        \dimen@ii\wd\tw@
        \setbox\tw@\hbox{\currentfont#2{}\currentskewchar}%
        \advance\dimen@ii-\wd\tw@
        \rlap{\raisebox{-#1}{$\m@th#3\kern\dimen@ii\widetilde{\phantom{#2}}$}}%
}
\makeatother

\newcommand\triple[1]{\wt{#1}_{\wt{#1}_{\wt{#1}}}}

\begin{document}
\Huge $\triple A\triple B\triple I\triple W\triple m\triple w$
\end{document}

(The amsmath package is only needed to make \widetilde work properly with \Huge and subscripts. What I find interesting about this solution is that it illustrates the role of the \skewchar in rule 12 of Appendix G in the TeXbook.)

The macro \wt should only be used for setting a tilde over a single "usual math letter". To be more precise, \wt{m} works, \wt{WA} works but doesn't position the tilde correctly, and the versions \wt{\gamma}, \wt{B_1} and \wt{\mathcal{M}} don't work at all. (Of course it would be possible to construct a macro that decides to use my solution in the case of a single "usual math letter", and TH's solution otherwise.)

Hendrik Vogt
  • 37,935