11

I'd like to modify the \int command such that it adds an automatic negative space, e.g. \! after the integral sign. Preferentially, I'd like to be able to not edit any of my code, so cleverly redefining \int seems like my best option. Unfortunately, naive implementations get the limits wrong:

\documentclass{article}
\usepackage[intlimits]{amsmath}
\usepackage{ifthen}

\newcommand{\diff}[2][]{
  \ifthenelse { \equal {#1} {} }
  {\ensuremath{\mathop{\mathrm{d} #2}}}
  {\ensuremath{\mathop{\mathrm{d}^#1 #2}}}
}

\begin{document}

This is what I would want:
\begin{equation}
 \int_0^\infty \mspace{-4mu}\diff[3]{\boldsymbol r} f(\boldsymbol r),
\end{equation}
but with this ``user code", containing no negative spacing explicitly:
\begin{verbatim}
 \int_0^\infty \diff[3]{\boldsymbol r} f(\boldsymbol r),
\end{verbatim}
which for comparison with the above gives:
\begin{equation}
\int_0^\infty \diff[3]{\boldsymbol r} f(\boldsymbol r),
\end{equation}

mostly because this gives me the option to remove it or change the spacing when I see fit.
\end{document}

Which results in:

rubenvb
  • 3,883
  • 4
  • 26
  • 36
  • Note I can't put it in \diff because that command is used everywhere, and sometimes the integral sign is followed by a fraction containing the \diff... – rubenvb Mar 18 '15 at 15:10
  • Are you open to a wrapper command \Integral or something like this? –  Mar 18 '15 at 15:24
  • @SoundsOfSilence Hmm that would take a lot of user code modification. I can write that myself, but asked here to see if there was a nice/hackish way to push that into \int. – rubenvb Mar 18 '15 at 15:26
  • The problem is the implicit \limits command which must be done first and then then negative spacing added –  Mar 18 '15 at 15:28
  • If you always add limits to the integral, it's easy, \def\int_#1^#2{\oldint_{#1}^{#2} \mspace{-4mu}}. In case you don't always have limits, it's a bit more trickier. In any case, let's see if someone comes with an “official” solution. – Manuel Mar 18 '15 at 15:29
  • @Manuel that's halfway there, I didn't know that was possible. Exactly the reason I keep asking weird questions like this. – rubenvb Mar 18 '15 at 15:30
  • @Manuel: That's what I had in my mind too, but the case with limits is cumbersome –  Mar 18 '15 at 15:31
  • @Manuel: In the sense that I've got no clue how to do it ;-) –  Mar 18 '15 at 15:34
  • @Manuel: Sorry, I did not mean \limits, but the integration limits in general –  Mar 18 '15 at 15:44
  • I would like \int to still add \limits as well (I used the intlimits option to amsmath in my example, which does just that. – rubenvb Mar 18 '15 at 15:45
  • @SoundsOfSilence If it's always input like \int_{..}^{..} then it's easy with the definition I did before. If not, with \@ifnextchar and a few auxiliar macros, or with xparse. – Manuel Mar 18 '15 at 16:10

3 Answers3

4

enter image description here

With this code

\usepackage{etoolbox}

\newcommand*\diff[2][]{\mathop{\mathrm{d}\ifblank{#1}{}{^{#1}}{#2}}}
\let\originalint\int
\def\int_#1^#2{\originalint_{#1}^{#2}\mathopen{}}

you remove space after the \int but your code must be written always with \int_{..}^{..} with limits and in that specific order. You could write some trickery so that if the limits are blank they are not added to the code. Or you could do some trickery with \@ifnextchar so that one checks for both limits in any order and the result is more clean. If it's wanted, I can add that. Here's the code (thanks to @kyle_the_hacker):

\usepackage{etoolbox}
\makeatletter
\newcommand*\diff[2][]{\mathop{\mathrm{d}\ifblank{#1}{}{^{#1}}{#2}}}
\let\int@original\int
\def\int{\int@checkfirstsb}
\def\int@checkfirstsb{\@ifnextchar_{\int@checksecondsp}{\int@checkfirstsp}}
\def\int@checkfirstsp{\@ifnextchar^{\int@checksecondsb}{\int@{}{}}}
\def\int@checksecondsp_#1{\@ifnextchar^{\int@grabsp{#1}}{\int@{#1}{}}}
\def\int@checksecondsb^#1{\@ifnextchar_{\int@grabsb{#1}}{\int@{}{#1}}}
\def\int@grabsb#1_#2{\int@{#2}{#1}}
\def\int@grabsp#1^#2{\int@{#1}{#2}}
\def\int@#1#2{\int@original\ifblank{#1}{}{_{#1}}\ifblank{#2}{}{^{#2}}\mathopen{}}
\makeatother

In any case, here's an oportunity to add a bit of code I wrote some time ago to add new kind of arguments to xparse.

\documentclass{scrartcl}
\usepackage[intlimits]{amsmath}
\usepackage{etoolbox,xparse}

\ExplSyntaxOn
\cs_new_protected:Npn \__xparse_count_type_k:w #1
 {
  \__xparse_single_token_check:n { #1 }
  \quark_if_recursion_tail_stop_do:Nn #1 { \__xparse_bad_arg_spec:wn }
  \__xparse_count_mandatory:N
 }
\cs_new_protected:Npn \__xparse_count_type_K:w #1 #2
 {
  \__xparse_single_token_check:n { #1 }
  \quark_if_recursion_tail_stop_do:nn { #2 } { \__xparse_bad_arg_spec:wn }
  \__xparse_count_mandatory:N
 }
\cs_new_protected:Npn \__xparse_add_type_k:w #1
 { \exp_args:NNo \__xparse_add_type_K:w #1 { \c__xparse_no_value_tl } }
\cs_new_protected:Npn \__xparse_add_type_K:w #1 #2
 {
  \__xparse_flush_m_args:
  \__xparse_add_grabber_optional:N K
  \tl_put_right:Nn \l__xparse_signature_tl { #1 { #2 } }
  \__xparse_prepare_signature:N
 }
\cs_new_protected:Npn \__xparse_add_expandable_type_k:w #1
 {
  \exp_args:NNo \__xparse_add_expandable_type_K:w #1 { \c__xparse_no_value_tl }
 }
\cs_new_protected_nopar:Npn \__xparse_add_expandable_type_K:w #1 #2
 {
  \__msg_kernel_error:nnx { xparse } { invalid-expandable-argument-type } { K }
  \__xparse_add_expandable_type_m:w % May be create this?
 }
\cs_new_protected:Npn \__xparse_grab_K:w #1 #2 #3 \l__xparse_args_tl
 {
  \__xparse_grab_K_aux:NnnNn #1 { #2 } { #3 } \cs_set_protected_nopar:Npn
   { _ignore_spaces }
 }
\cs_new_protected:Npn \__xparse_grab_K_long:w #1 #2 #3 \l__xparse_args_tl
 {
  \__xparse_grab_K_aux:NnnNn #1 { #2 } { #3 } \cs_set_protected:Npn
   { _ignore_spaces }
 }
\cs_new_protected:Npn \__xparse_grab_K_trailing:w #1 #2 #3 \l__xparse_args_tl
 {
  \__xparse_grab_K_aux:NnnNn #1 { #2 } { #3 } \cs_set_protected_nopar:Npn
   { _ignore_spaces }
 }
\cs_new_protected:Npn \__xparse_grab_K_long_trailing:w #1 #2 #3 \l__xparse_args_tl
 {
  \__xparse_grab_K_aux:NnnNn #1 { #2 } { #3 } \cs_set_protected:Npn
   { _ignore_spaces }
 }
\cs_new_protected:Npn \__xparse_grab_K_aux:NnnNn #1 #2 #3 #4 #5
 {
  \exp_after:wN #4 \l__xparse_fn_tl ##1
   {
    \__xparse_add_arg:n { ##1 }
    #3 \l__xparse_args_tl
   }    
  \use:c { peek_meaning_remove #5 :NTF } #1
   { \l__xparse_fn_tl }
   {
    \__xparse_add_arg:n { #2 }
    #3 \l__xparse_args_tl
   }
 }

\prop_put:Nnn \c__xparse_shorthands_prop { a } { k \sb }
\prop_put:Nnn \c__xparse_shorthands_prop { b } { k \sp }
\prop_put:Nnn \c__xparse_shorthands_prop { A } { K \sb }
\prop_put:Nnn \c__xparse_shorthands_prop { B } { K \sp }

\NewDocumentCommand \diff { o m }
  {\mathop{\mathrm{d}\IfValueT{#1}{^{#1}}{#2}}}
\ExplSyntaxOff

\let\originalint\int
\RenewDocumentCommand \int { a b }
  {\originalint\IfValueT{#1}{_{#1}}\IfValueT{#2}{^{#2}}\mathopen{}}

\begin{document}

\begin{equation}
\int_0^\infty \diff[3]{\boldsymbol r} f(\boldsymbol r)
\end{equation}

\end{document}

And it may be even better to check if \diff follows and in case it doesn't just have the original behaviour, just change the definition to this (it's a pity that spaces aren't skipped, so we need to explicitly check for a space and then check for \diff after that)

\makeatletter
\RenewDocumentCommand \int { a b t\@sptoken t\diff }
  {\originalint\IfValueT{#1}{_{#1}}\IfValueT{#2}{^{#2}}%
   \IfBooleanT{#4}{\mathopen{}\diff}}
\makeatother

After a few thoughts

Completed rethink code that I would use for a complete flexibility. This adds all possible combinations of sub-superscripts for the \int plus the natural input for differentials \dd x, \dd{\bm x}, \dd^2 x, \dd^{a_1}{x_1}.

\usepackage{mathtools,bm}
\usepackage{etoolbox}
\makeatletter
\def\dd{\@ifnextchar^{\dd@grabsp}{\dd@{}}}
\def\dd@grabsp^#1#2{\dd@{#1}{#2}}
\def\dd@#1#2{\mathop{d\ifblank{#1}{}{^{#1}}{#2}}}
\let\int@original\int
\def\int{\int@checkfirstsb}
\def\int@checkfirstsb{\@ifnextchar_{\int@checksecondsp}{\int@checkfirstsp}}
\def\int@checkfirstsp{\@ifnextchar^{\int@checksecondsb}{\int@{}{}}}
\def\int@checksecondsp_#1{\@ifnextchar^{\int@grabsp{#1}}{\int@{#1}{}}}
\def\int@checksecondsb^#1{\@ifnextchar_{\int@grabsb{#1}}{\int@{}{#1}}}
\def\int@grabsb#1_#2{\int@{#2}{#1}}
\def\int@grabsp#1^#2{\int@{#1}{#2}}
\def\int@#1#2{\int@original\ifblank{#1}{}{_{#1}}\ifblank{#2}{}{^{#2}}\mathopen{}}
\makeatother

that with the xparse code from before it would become

\NewDocumentCommand\dd{bm}{\mathop{d\IfValueT{#1}{^{#1}}{#2}}}
\let\originalint\int
\RenewDocumentCommand\int{ab}{\originalint\IfValueT{#1}{_{#1}}\IfValueT{#2}{^{#2}}\mathopen{}}
Manuel
  • 27,118
  • I tried something like: \makeatletter \let\originalint\int \def\int\@ifnextchar_{\@int}{\@int_{}^{}} \def\@int_#1^#2{\originalint_{#1}^{#2}\mathopen{}} \makeatother but it don't work, could you add the correct way to do it to you answer? Thanks. – Kyle_the_hacker Feb 09 '16 at 20:55
  • 1
    \def\int{\@ifnextchar_{\@int}{\@int_{}^{}}} at least there you got a missing pair of {..}. – Manuel Feb 09 '16 at 21:22
  • Yeah, ok. That's because I tried a lot of stuff. I still become the error: You can't use `\spacefactor' in display math mode. (so obviously a problem with the @) – Kyle_the_hacker Feb 09 '16 at 22:01
  • 1
    You are not using \makeatletter. This should compile fine \makeatletter \let\originalint\int \def\int{\@ifnextchar_{\@int}{\@int_{}^{}}} \def\@int_#1^#2{\originalint_{#1}^{#2}\mathopen{}} \makeatother – Manuel Feb 09 '16 at 22:03
  • You are right, they were mispositioned. Edited your answer. – Kyle_the_hacker Feb 09 '16 at 22:07
  • I rejected the edit. While it was correct, it's too simple. It is not what I intended to put there. I hope you don't mind; I added a complete solution (unless I made an error). It does work in all cases \int \diff, \int_a \diff, \int^a \diff, \int_a^b \diff, \int^a_b \diff. – Manuel Feb 09 '16 at 22:25
2

This has become really easy with the newest release of xparse:

\documentclass{article}
\usepackage[intlimits]{amsmath}
\usepackage{bm}
\usepackage{xparse}

\let\oldint\int % for the comparison

\RenewDocumentCommand{\int}{e{_^}}{%
  \DOTSI
  \intop_{\IfValueT{#1}{#1}}^{\IfValueT{#2}{#2}}%
  \mspace{-4mu}%
}

\NewDocumentCommand{\diff}{om}{% equivalent, but easier with xparse
  \mathop{\mathrm{d}\IfValueT{#1}{^{#1}}#2}
}

\begin{document}

This is the normal typesetting
\begin{equation}
 \oldint_0^\infty \diff[3]{\bm{r}} f(\bm{r}),
\end{equation}
This is what I would want:
\begin{equation}
 \oldint_0^\infty \mspace{-4mu}\diff[3]{\bm{r}} f(\bm{r}),
\end{equation}
and this is what we get
\begin{equation}
 \int_0^\infty \diff[3]{\bm{r}} f(\bm{r}),
\end{equation}

\end{document}

enter image description here

However, this is a false problem, mainly due to the spurious space inserted by the faulty definition of \diff.

Indeed, compare with

\documentclass{article}
\usepackage[intlimits]{amsmath}
\usepackage{bm}
\usepackage{xparse}

\NewDocumentCommand{\diff}{om}{% note \!
  \mathop{\!\mathrm{d}\IfValueT{#1}{^{#1}}#2}
}

\begin{document}

This is the normal typesetting
\begin{equation}
 \int_0^\infty \diff[3]{\bm{r}} f(\bm{r}),
\end{equation}

\end{document}

enter image description here

egreg
  • 1,121,712
1

With the previously posted answer being HUGE and has complex dependencies, I tried to come up with a more compact solution, and arrived at the following:

Code

Drop this anywhere in the document, before the first use of \int.

\makeatletter
\let\MyIntOrig\int
\def\MyIntSpace{\hspace{-.5em}} %% Configure as needed.
\def\int{\MyInt}
\def\MyInt{\MyIntOrig\MyIntSkipMaybe}
\def\MyIntSkipMaybe{
  \@ifnextchar_{\MyIntSkipScript}{%
  \@ifnextchar^{\MyIntSkipScript}{%
  \@ifnextchar\limits{\MyIntSkipTok}{%
  \@ifnextchar\nolimits{\MyIntSkipTok}{%
  %% Add more cases here as needed. Keep in mind to close the braces.
  \MyIntSpace}}}}%
}
\def\MyIntSkipScript#1#2{#1{#2}\MyIntSkipMaybe}
\def\MyIntSkipTok#1{#1\MyIntSkipMaybe}
\makeatother

Minimal working document

enter image description here

\documentclass[12pt]{article}
\usepackage{amsmath}

\begin{document}

\makeatletter
\let\MyIntOrig\int
\def\MyIntSpace{\hspace{-.5em}} %% Configure as needed.
\def\int{\MyInt}
\def\MyInt{\MyIntOrig\MyIntSkipMaybe}
\def\MyIntSkipMaybe{
  \@ifnextchar_{\MyIntSkipScript}{%
  \@ifnextchar^{\MyIntSkipScript}{%
  \@ifnextchar\limits{\MyIntSkipTok}{%
  \@ifnextchar\nolimits{\MyIntSkipTok}{%
  %% Add more cases here as needed. Keep in mind to close the braces.
  \MyIntSpace}}}}%
}
\def\MyIntSkipScript#1#2{#1{#2}\MyIntSkipMaybe}
\def\MyIntSkipTok#1{#1\MyIntSkipMaybe}
\makeatother


\def\MyTry#1{%
  & {\let\int\MyIntOrig #1} &
  & #1 &
  & \text{\ttfamily\detokenize{#1}}&
}
\begin{align*}
  & \text{Original} && \text{Redefined} && \text{Source} &\\
  \MyTry{\int }\\
  \MyTry{\int \int }\\
  \MyTry{\int a\,dx}\\
  \MyTry{\int _b a\,dx}\\
  \MyTry{\int ^c a\,dx}\\
  \MyTry{\int _b^c a\,dx}\\
  \MyTry{\int _{from}^{to} a\,dx}\\
  \MyTry{\int \limits_{from}^{to} a\,dx}\\
  \MyTry{\int \nolimits_{from}^{to} a\,dx}\\
  \MyTry{\int a_b x}
\end{align*}

\end{document}
kdb
  • 1,889