hooks,lthooks – How to Reset/Clear a Hook and Print a Background Picture Only Once

hookslthooks

I'd like to add a picture in the background of a single page, but if I use \AddToHook it appears on every page of the document. If I want to clear it after it appears on that specific page (so only one page), how can I do this? I thought \ClearHookNext would work but I get Undefined control sequence (macOS 12.1, latest TeXLive 2021).

\documentclass{scrartcl}
\usepackage{tikz}
\usepackage{blindtext}
\begin{document}
\blindtext[10]
\AddToHook{shipout/background}{\put(0,0){%
\begin{tikzpicture}[remember picture, overlay, shift={(current page.center)}]
   \draw[fill, color=red] (0,0) circle (2cm);
\end{tikzpicture}%
}}
\blindtext[10]
% Trials:
% \RemoveFromHook{shipout/background}% removes all (so also the first one we wanted to print)
%\ClearHookNext{shipout/background}% Undefined control sequence
\end{document}

Update: A different, but related problem would then be how to switch off a hook after a couple of pages (when you don't know the exact page number beforehand as the content is variable in length). Using \RemoveFromHook at that point draws no background picture at all (although one would expect it to draw them between the \AddToHook and \RemoveFromHook commands):

\documentclass{scrartcl}
\usepackage{tikz}
\usepackage{blindtext}
\begin{document}
\blindtext[10]
\AddToHook{shipout/background}{\put(0,0){%
\begin{tikzpicture}[remember picture, overlay, shift={(current page.center)}]
   \draw[fill, color=red] (0,0) circle (2cm);
\end{tikzpicture}%
}}
\blindtext[10]
\RemoveFromHook{shipout/background}
\blindtext[10]
\end{document}

Best Answer

You can use \AddToHookNext{<hook>} which adds code to be executed only the next time the <hook> is called. The document below prints 5 pages, and the third has a red circle in the middle:

\documentclass{scrartcl}
\usepackage{tikz}
\usepackage{blindtext}
\begin{document}
\blindtext[10] % blank line below required

\AddToHookNext{shipout/background}{\put(0,0){%
\begin{tikzpicture}[remember picture, overlay, shift={(current page.center)}]
   \draw[fill, color=red] (0,0) circle (2cm);
\end{tikzpicture}%
}}
\blindtext[10]
\end{document}

The blank line between the first \blindtext and \AddToHookNext is needed so that TeX processes \blindtext[10] (breaking it into paragraphs and those paragraphs into pages, thus shipping pages out) and then when you call \AddToHookNext you are on the right page. If you don't add the blank line, by the time you call \AddToHookNext TeX is still processing the first page (even though it has two full pages of content stored in memory), so the circle ends up in the wrong place.


Another way, if you want to place the circle in a specific page (rather than "right here") is to test for the page number:

\documentclass{scrartcl}
\usepackage{tikz}
\usepackage{blindtext}
\begin{document}
\blindtext[10]
\AddToHook{shipout/background}{%
  \ifnum\value{page}=3 % only on page 3
    \put(0,0){%
    \begin{tikzpicture}[remember picture, overlay, shift={(current page.center)}]
       \draw[fill, color=red] (0,0) circle (2cm);
    \end{tikzpicture}%
    }%
  \fi}
\blindtext[10]
\end{document}

If you want to turn your code on and off in the hook in the middle of the document (page number unknown) then you need a finer-grained control provided by rules.

You can, as in the example below, label the circle code as ./mycircle, and add another blank chunk of code labelled ./stop-mycircle. By default, both chunks added are executed (the second is blank, so it does no harm). Then, when you want to deactivate the circle background you write

\DeclareHookRule{shipout/background}{./stop-mycircle}{voids}{./mycircle}

so that the presence of ./stop-mycircle in the hook will stop ./mycircle from executing. Then you can write

\ClearHookRule{shipout/background}{./mycircle}{./stop-mycircle}

to remove that "voids" rule and allow ./mycircle executing again.

The big advantage of this method is that you can use these commands on and off at your will: none will act destructively on the code you added to the hook, so you can always revert. In fact, \RemoveFromHook is better, in most cases, replaced by voids.

Here is the code (whose output contains circles on pages 3, 4 and 5 only):

\documentclass{scrartcl}
\usepackage{tikz}

\AddToHook{shipout/background}[./mycircle]{\put(0,0){%
  \begin{tikzpicture}[remember picture, overlay, shift={(current page.center)}]
    \draw[fill, color=red] (0,0) circle (2cm);
  \end{tikzpicture}}}
\AddToHook{shipout/background}[./stop-mycircle]{}
\newcommand\circleon{%
  \ClearHookRule{shipout/background}{./mycircle}{./stop-mycircle}}
\newcommand\circleoff{%
  \DeclareHookRule{shipout/background}{./stop-mycircle}{voids}{./mycircle}}
\circleoff % initially off

\begin{document}
page 1 \clearpage
page 2 \clearpage
page 3 \circleon \clearpage % start showing on page 3
page 4 \clearpage
page 5 \clearpage
page 6 \circleoff \clearpage % no longer show on page 6
page 7 \clearpage
\end{document}

\ClearHookNext shouldn't be read as "clear the hook after the next execution", but as "clear the 'next execution' code", so it removes code added with \AddToHookNext. To remove code added with \AddToHook you have to use either \RemoveFromHook or add another code label with a rule that voids the one you want to remove.

Related Question