3

I was trying to do some debugging in math mode related to spacing. More specific, I wanted to create some vertical rulers to check for discrepancies in horizontal alignment in math mode. With this question I want to provide general help for debugging. I struggled with it, also to find good resources and related questions here, so I want to tackle the issue of not finding any works about general debugging in Latex math mode. I was impressed by mhelvens' question (How to fix the math-spacing when a lot of \phantom, \mathrlap, etc. are used?) and wanted to create a similar output with lines/rulers as in the last picture he provided:

target

On my way to get these rulers, I faced some problems.

Resources

What I looked into might be helpful for others as well:

Collection of related questions

I am just listing those as helpful addition. They were partly hard to find or not tagged as debugging related, so it might be a nice resource as well.

Vertical expansion of math environments

One problem that arose is that math environments always expand (vertically) according to their content.

edit: Using \smash could solve this first problem. Thanks Circumscribe for pointing there! More info in the resource by Alexander R. Perlis.

Horizontal spacing differs using \mathrlap

\mathrlap, as an equivalent to \rlap in math mode, should not take any horizontal space. But it does change the spacing. Consider the following example:

\documentclass[varwidth=3cm,margin=2mm]{standalone}

\usepackage{amsmath}
\usepackage{mathtools}
\usepackage{xcolor}

\AtBeginDocument{%
    \abovedisplayskip=0pt%12pt plus 3pt minus 9pt
    \abovedisplayshortskip=0pt%0pt plus 3pt
    \belowdisplayskip=0pt%12pt plus 3pt minus 9pt
    \belowdisplayshortskip=0pt%7pt plus 3pt minus 4pt
}


\begin{document}
\noindent%
\begin{align} % two plain \mathrlaps (red)
    \Phi = \mathrlap{\smash{\color{red}\vrule depth 5pt height 10pt width .2pt}}\sum
    \mathrlap{\smash{\color{red}\vrule depth 20pt height 10pt width .2pt}}
\end{align}

\begin{align} % one plain \mathrlap (orange)
    \Phi = \sum
    \mathrlap{\smash{\color{orange}\vrule depth 20pt height 10pt width .2pt}}
\end{align}

\begin{align} % target spacing (untouched)
    \Phi = \sum
\end{align}

\begin{align} % one \mathrlap in the beginning: no issues (green)
    \mathrlap{\smash{\color{green}\vrule depth 30pt height 80pt width .2pt}}
    \Phi = \sum
\end{align}

\begin{align} % corrected \mathrlaps (blue)
    \Phi = \mathopen{\mathrlap{\smash{\color{blue}\vrule depth 5pt height 95pt width .2pt}}}
    \sum
    \mathclose{\mathrlap{\smash{\color{blue}\vrule depth 5pt height 60pt width .2pt}}}
\end{align}
\end{document}

mathrlap changing horizontal spacing

(1) has two plain rlaps: before and after the sum (red).
(2) has one plain rlap: after the sum (orange).
(3) has no rlaps at all (target layout).
(4) has one plain rlap: before the equation (green).
(5) has two corrected rlaps: before and after the sum (blue).

So how can one achieve proper spacing around rlaps in math mode? It is related to math atoms, see this question with a great answer by egreg: What's the right space to right the alignment of a right aligned align environment?

In my example code above, you can see that rlaps in the beginning don't affect positioning. I forced the other rlaps manually to be considered as fitting atom types. How can I automate this? Maybe this question can help to improve the \mathrlap macro in the mathtools package.


In Circumscribe's answer, the double-printing issue can be fixed using

\newcommand*\printlater@printnow[2]{%
  \@ifundefined{printlater@anchorx@#1}{}{%
    \AtBeginShipoutNext{%
      \AtBeginShipoutUpperLeft{%
        \message{*** PRINTING NO #1 ***
        }%
        \@ifundefined{printlater@printed@#1}{\message{***UNDEF***}%
          \setlength{\unitlength}{1sp}%
          \put(\@nameuse{printlater@anchorx@#1},\the\numexpr\@nameuse{printlater@anchory@#1}-\paperheight){#2}%
          \expandafter\gdef\csname printlater@printed@#1\endcsname{\relax}
        }{\message{***DEF***}}%
      }%
    }%
  }%
}

You can safely delete the \messages, they were just for debugging.

  • Is there hope to see how you input the diagram you want to “debug”? – egreg Jul 3 '18 at 17:12
  • it is not clear what your question is, clearly the vrule increases the height of the equation as it has 20pt height, placing \mathclap in a mathrlap does nothing as the mathclap is already zero width. – David Carlisle Jul 3 '18 at 21:13
  • You may want to look at \smash, which is the vertical equivalent of \llap/\clap/\rlap (and their math versions, smash tests if you are in math mode). You could use \clap{\smash{\vrule ...}}, but this doesn't solve your second problem. – Circumscribe Jul 3 '18 at 21:57
  • @egreg and the others: I clarified my intention and overhauled the question greatly. – nox Jul 4 '18 at 7:15
  • 1
    Speaking of debugging spacing, you might also want to check out the lua-visual-debug package. It visualises glue, kerns, hyphenation points, boxes and penalties. As the name suggests, it uses LuaLaTeX. – Circumscribe Jul 4 '18 at 16:44
3

Here is a rather elaborate solution that should not affect any spacing (either horizontal or vertical) at all. Instead of actually printing the \vrule immediately, you could store the coordinates where you would like it to be printed and print it at this location just before shipout (which is when the page is actually being created).

The following code requires two runs to work and I've tested it with LaTeX, pdfLaTeX, LuaLaTeX and XeLaTeX. The part between \begin{document} and \end{document} is identical to yours, except I've replaced \mathrlap{\smash{...}} by \printlater{...}.

(This code appears to be incompatible with the standalone class, probably because \paperheight isn't the actual paper height.)

%\documentclass[varwidth=3cm,margin=2mm]{standalone} %% <- does not work, not sure why
\documentclass[a4paper]{article} %% <- paper size option is important!

\usepackage{amsmath} 
%% ^ remove \ifmeasuring@\else and \fi below if you don't use amsmath
% \usepackage{mathtools} %% <- not needed
\usepackage{xcolor}

\AtBeginDocument{%
    \abovedisplayskip=0pt%12pt plus 3pt minus 9pt
    \abovedisplayshortskip=0pt%0pt plus 3pt
    \belowdisplayskip=0pt%12pt plus 3pt minus 9pt
    \belowdisplayshortskip=0pt%7pt plus 3pt minus 4pt
}

\usepackage{atbegshi} %% <- for \AtBeginShipout and \AtBeginShipoutUpperLeft

\makeatletter %% <- change @ so that it can be used in command names

  \@ifundefined{savepos}
  {  %% <- For pdflatex and xelatex
    \let\savepos\pdfsavepos
    \let\lastxpos\pdflastxpos
    \let\lastypos\pdflastypos
  }{} %% <- For lualatex
  \newcounter{printlatercounter} %% <- unique ids for things we want to print later
  \newcommand*\printlater[1]{%
    \ifmeasuring@\else %% <- Do nothing if in amsmath's "measuring" mode
      \stepcounter{printlatercounter}%
      \expandafter\printlater@writepos\expandafter{\number\c@printlatercounter}%
      \expandafter\printlater@printnow\expandafter{\number\c@printlatercounter}{#1}%
    \fi
  }
  \newcommand*\printlater@writepos[1]{% %% <- Writes the current coordinates to the aux file
    \savepos %% <- get current position on page
    \write\@auxout{\global\string\@namedef{printlater@anchorx@#1}{\the\lastxpos}}% %% <- write x coord.
    \write\@auxout{\global\string\@namedef{printlater@anchory@#1}{\the\lastypos}}% %% <- write y coord.
  }
  \newcommand*\printlater@printnow[2]{% %% <- Prints #2 at the #1-th set of coordinates
    \@ifundefined{printlater@anchorx@#1}{}{% %% <- test if the coordinates are set
      \AtBeginShipoutNext{% %% <- execute just before shipout
        \AtBeginShipoutUpperLeft{% %% <- insert things at the top left corner of the page
          \setlength{\unitlength}{1sp}% %% <- coordinates are in sp (65536sp = 1pt)
          \put(\@nameuse{printlater@anchorx@#1},\the\numexpr\@nameuse{printlater@anchory@#1}-\paperheight){#2}%
        }%
      }%
    }%
  }

\makeatother %% <- return @ to normal

\begin{document}

\begin{align*} % two plain \printlaters (red)
    \Phi = \printlater{\color{red}\vrule depth 5pt height 10pt width .2pt}\sum
    \printlater{\color{red}\vrule depth 20pt height 10pt width .2pt}
\end{align*}

\begin{align*} % one plain \printlater (orange)
    \Phi = \sum
    \printlater{\color{orange}\vrule depth 20pt height 10pt width .2pt}
\end{align*}

\begin{align*} % target spacing (untouched)
    \Phi = \sum
\end{align*}

\begin{align*} % one \printlater in the beginning (green)
    \printlater{\color{green}\vrule depth 30pt height 80pt width .2pt}
    \Phi = \sum
\end{align*}

\begin{align*} % corrected \printlaters (blue)
    \Phi = \mathopen{\printlater{\color{blue}\vrule depth 5pt height 95pt width .2pt}}
    \sum
    \mathclose{\printlater{\color{blue}\vrule depth 5pt height 60pt width .2pt}}
\end{align*}

\end{document}

Note: the [a4paper] option provided to \documentclass is important because its ensures that the value of \paperheight (which we need) is correct. By default, \paperheight has a value of 279.4mm (letter size) even if you are producing an a4 document, which would cause all rules to be offset by 297mm - 279.4mm = 17.6mm. (This only happens if the xcolor package is also removed, because loading this package also changes the value of \paperheight for some reason.)

enter image description here

The macro \printlater which I've defined above takes one argument and prints it at the current location just before shipout. It does four things

  • It first checks if we are in amsmath's "measuring mode" using \ifmeasuring@, and if we are it does nothing. Without this check, the argument of \printlater would be printed twice whenever it is used inside e.g. an align environment. (See this question.)
  • It increment printlatercounter, which generates unique ids for the things we want to be printed.
  • It retrieves the current x/y-coordinates using \(pdf)savepos and \(pdf)last<x/y>pos and writes them to the aux files as \printlater@anchor<x/y>@<id>. These coordinates are in sp (65536sp = 1pt) and relative to the bottom left corner of the page.
  • If printlater@x@<id> is defined (which will be the case on a second run), it instructs TeX to print the argument right before shipping the page. The macros \AtBeginShipoutNext and \AtBeginShipoutUpperLeft from the atbegshi package take care of this.

The reason why two runs are needed is because the exact coordinates of a piece of content are not known until the page is shipped, at which time it will be too late to print anything else. We can however still write these coordinates to the aux file and then use them on the second run.

If you want to manually shift one of your \vrules, say by .5pt to the left, you can just precede it by a \hskip-.5pt (within the \printlater).

The content that is drawn at shipout is currently in the background, behind the text (you don't notice because it doesn't touch any of the text). This might be desirable, but if you prefer it to be in the foreground you need to use \AtBeginShipoutUpperLeftForeground instead of \AtBeginShipoutUpperLeft,


You could also use TikZ instead of \put to print things. You'll need to load TikZ and use:

\newcommand*\printlater@printnow[2]{%
  \@ifundefined{printlater@anchorx@#1}{}{%
    \AtBeginShipoutNext{%
      \AtBeginShipoutUpperLeftForeground{%
        \tikz[overlay,remember picture]
          \node[anchor=base]
            at ([xshift=\@nameuse{printlater@anchorx@#1}sp,
                 yshift=\@nameuse{printlater@anchory@#1}sp]current page.south west)
                   {\rlap{#2}};%
      }%
    }%
  }%
}

instead of the earlier definition. I see no reason why you would currently want to do this, but it is useful to know if you at some point decide that you would like to draw more complicated things than horizontal/vertical lines.


Edit: Here is another test, which compares the spacing around the original \colon, amsmath's \colon, :, \mathpunct: and \mathord:. The position of the coloured lines is automated, and the grey lines are each 1pt apart (starting from the red line).

\documentclass[a4paper]{article} %% <- paper size option is important!

\let\oldcolon\colon
\usepackage{amsmath}
%% ^ remove \ifmeasuring@\else and \fi below if you don't use amsmath
\let\amsmathcolon\colon

\usepackage{xcolor}

\usepackage{atbegshi} %% <- for \AtBeginShipout and \AtBeginShipoutUpperLeft

\makeatletter %% <- change @ so that it can be used in command names
  \@ifundefined{savepos}
  {  %% <- For pdflatex and xelatex
    \let\savepos\pdfsavepos
    \let\lastxpos\pdflastxpos
    \let\lastypos\pdflastypos
  }{} %% <- For lualatex
  \newcounter{printlatercounter} %% <- unique ids for things we want to print later
  \newcommand*\printlater[1]{%
    \ifmeasuring@\else %% <- Do nothing if in amsmath's "measuring" mode
      \stepcounter{printlatercounter}%
      \expandafter\printlater@writepos\expandafter{\number\c@printlatercounter}%
      \expandafter\printlater@printnow\expandafter{\number\c@printlatercounter}{#1}%
    \fi
  }
  \newcommand*\printlater@writepos[1]{% %% <- Writes the current coordinates to the aux file
    \savepos %% <- get current position on page
    \write\@auxout{\global\string\@namedef{printlater@anchorx@#1}{\the\lastxpos}}% %% <- write x coord.
    \write\@auxout{\global\string\@namedef{printlater@anchory@#1}{\the\lastypos}}% %% <- write y coord.
  }
  \newcommand*\printlater@printnow[2]{% %% <- Prints #2 at the #1-th set of coordinates
    \@ifundefined{printlater@anchorx@#1}{}{% %% <- test if the coordinates are set
      \AtBeginShipoutNext{% %% <- execute just before shipout
        \AtBeginShipoutUpperLeft{% %% <- insert things at the top left corner of the page
          \setlength{\unitlength}{1sp}% %% <- coordinates are in sp (65536sp = 1pt)
          \put(\@nameuse{printlater@anchorx@#1},\the\numexpr\@nameuse{printlater@anchory@#1}-\paperheight){#2}%
        }%
      }%
    }%
  }
\makeatother %% <- return @ to normal

\begin{document}

\newcommand*\myrule[2][0pt]{\printlater{\hskip#1\color{#2}\vrule depth 5pt height 10pt width .2pt}}
\newcommand*\grayrules{\myrule[1pt]{gray}\myrule[2pt]{gray!50!white}\myrule[3pt]{gray!25!white}\myrule[4pt]{gray!12.5!white}}

\begin{align*}
    f\myrule{red}\grayrules\oldcolon \myrule{green}{\myrule{blue}X} &\to Y
\\
    f\myrule{red}\grayrules\amsmathcolon \myrule{green} {\myrule{blue}X} &\to Y
\\
    f\myrule{red}\grayrules: \myrule{green} {\myrule{blue}X} &\to Y
\\
    f\myrule{red}\grayrules\mathpunct: \myrule{green} {\myrule{blue}X} &\to Y
\\
    f\myrule{red}\grayrules\mathord: \myrule{green} {\myrule{blue}X} &\to Y
\end{align*}

\end{document}

enter image description here


Edit: Added a comment about the necessity of the a4paper option and fixed the double-printing problem. Thanks to @nox for pointing these things out.

  • Wow, nice work and definitely a good answer in here as you can debug all kinds of stuff with that! It just feels a little more like a work-around rather than a fix for the \mathrlap issue. – nox Jul 4 '18 at 14:51
  • @nox: I expect that any method to make the box created by \mathrlap truly invisible, space-wise, will probably involve some Lua code. And I unfortunately know nothing about LuaLaTeX :(. – Circumscribe Jul 4 '18 at 16:39
  • It actually is working if you adjust manually the math atom. Talking about automation... If that were true, I got a great answer by you :) So let's see what others say. If that is true or they remain silent I will accept your answer, as it does solve the problem. – nox Jul 4 '18 at 16:59
  • While investigating strange amsmath behavior (align environments) for this question, I was using your code and figured out that the positioning will break when not loading the xcolor package. Alternatively, you can load pgfpages. Whatever they have in common, it fixes the positioning. You could add that info to your answer. Also, in an align environment, this solution will print everything twice over itself, which shouldn't be too much of a problem. The text looks a little thicker, though. – nox Jul 18 '18 at 4:32
  • You can fix the double-printing issue with the code I provided in my question. – nox Jul 18 '18 at 5:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.