1

I use only lualatex, but perhaps this is a general LaTeX question. Assume that TeXlive 2023 or later is used, since there is no need for backwards compatibility.

I have looked at atbegshi and some other docs, but they do not do what I need, or are much more complex than necessary. I also see that, not too long ago, someone asked a similar question, but without a useful answer: Use shipout hooks to manipulate global state

Situation: I have two commands, which I will call \foo and \unfoo. The \foo command includes \clearpage at its top, so I do not need to worry about where \foo appears when typeset. The \unfoo command finishes with \clearpage.

If a page has \foo, then the same page must have \unfoo. If the user fails to write \unfoo before the \foo page ships, it is an error. I know how to write a suitable error message, halting compile. I do not need to store, edit, discard, or otherwise manipulate the page contents.

My question: Is there a simple way to use the \shipout hook (or something similar) to do this? Although atbegshi looks like it should be capable, there is a lot going on with that package, which makes me nervous.

MWE:

\documentclass{article} % Compile with lualatex.
\usepackage{fontspec}
\newif\ifusedfoo
\def\foo{\clearpage Hello.\par\usedfootrue}
\def\unfoo{Goodbye.\par\usedfoofalse\clearpage}

% \WhenPageShips{ % This is what I need. I know how to create an error, instead of typeout. % \ifusedfoo % \typeout{EEEEK. The page with \string\foo\space did not have \string\unfoo.} % \fi % }

\begin{document} Welcome.\par \foo No problem here.\par \unfoo \foo % With too many lines on that page, causes problem: x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\ x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\ x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\ x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\x\par % Whether or not there is a later \unfoo, does not matter. Moving right along\par \clearpage This is too late.\par \unfoo I cannot wait until this far in the document.\par \end{document}

Also: I do not wish to use an aux file for this. The code does not appear within anything complicated (such as bibliography, table of contents, etc.). Just within running text.

EDIT: Made it clear that the problem should be detected quickly, not later in the document.

EDIT2: The accepted solution works in my specific situation but may not work in the general situation. See comments.

rallg
  • 2,379
  • 1
    Unrelated, but using \setmainfont{Latin Modern Roman} is unnecessary. The default font with lualatex and xelatex is LM only. You can remove that line safely without any changes in the output. – Niranjan Feb 08 '24 at 04:18
  • @Niranjan Good catch. Actually, I do not use that font (hee hee). – rallg Feb 08 '24 at 04:19
  • Ahh, okay, since now you don't have \setmainfont, you don't really need \usepackage{fontspec} too... ;-) – Niranjan Feb 08 '24 at 04:39
  • I am not aware of shipout-mechanisms, but you may want to go through ltshipout-doc.pdf. – Niranjan Feb 08 '24 at 04:44
  • Yes, using marks should be sufficient for this purpose. Start with texdoc source2e, read ltmarks and ltshipout (assume you're proficient enough) // possibly related: https://tex.stackexchange.com/questions/18849/how-do-i-check-whether-the-text-will-fit-in-one-page – user202729 Feb 08 '24 at 06:21
  • You probably want to read lthooks-doc before ltshipout-doc unless you're already familiar with the hooks stuff. See page 7 of ltshipout-doc: if you load atbegshi with current LaTeX, you're loading an emulation (atbegshi-ltx.sty), but it's recommended to use the hooks directly in new code. – cfr Feb 08 '24 at 07:26
  • The question you linked answers your question. The problem in the linked question is due to the complexities of colour stuff. There's no such complication if you just want to generate an error or output a message. – cfr Feb 08 '24 at 07:40
  • @cfr Ah, I did not catch that. The "colour" stuff threw me off. – rallg Feb 08 '24 at 15:28
  • @user202729 The solution by cfr is simpler than I had imagined. But I will take your advice and look at ltshipout-doc for instructional purposes. – rallg Feb 08 '24 at 15:41
  • @rallg If that were to work, Knuth wouldn't have to design the complicated mark mechanism in TeX. – user202729 Feb 08 '24 at 21:13
  • @user202729 Indeed, what you say is why I asked the question; prior search turned out complicated things. But it does work, with the simple solution given by cfr. This is probably because I do not use a standard LaTeX document class, no aux file, no bibliography, no table of contents, no automated this-or-that. Just lengthy passages of non-academic text. The \foo...\unfoo is because the actual command (not foo) is similar to a LaTeX command that does something else. This is a way to trap the situation where users write the wrong command. – rallg Feb 08 '24 at 21:50
  • @rallg See my comment below that answer. – user202729 Feb 08 '24 at 21:50
  • 1
    @rallg the accepted solution isn't 100% safe but will probably get the right answer just because \unfoo is defined do do \par before \usedfoofalse without that it wouldn't work at all, so as long as your real document commands have that then you may be ok with this. – David Carlisle Feb 08 '24 at 22:21
  • @DavidCarlisle Good point. There are many places in my custom document class where \par is automatically inserted, if it is in horizontal mode there. – rallg Feb 08 '24 at 22:40
  • 1
    the point is that par excercises the page breaker, the easiest way to show the shiphout hook failing would be to have \foo and \unfoo in the same paragraph with a page break between but with the specific definitions here that way is excluded, – David Carlisle Feb 08 '24 at 22:52

1 Answers1

2
\documentclass{article} % Compile with lualatex.
\usepackage{fontspec}
\newif\ifusedfoo
\def\foo{\clearpage Hello.\par\usedfootrue}
\def\unfoo{Goodbye.\par\usedfoofalse\clearpage}

\AddToHook{shipout/before}[rallg]{%
% \WhenPageShips{ % This is what I need. I know how to create an error, instead of typeout.
  \ifusedfoo
    \typeout{EEEEK. The page with \string\foo did not have \string\unfoo.}
  \fi
}

\begin{document}
Welcome.\par
\foo
No problem here.\par
\unfoo
\foo % With too many lines on that page, causes problem:
x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\
x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\
x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\
x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x\par
% Whether or not there is a later \unfoo, does not matter.
\end{document}

This types out the message for the final two pages since both occur without \unfoo. If you want to avoid the second, you'll need to manage the conditional logic appropriately.

cfr
  • 198,882
  • So simple. Other question on this topic usually involved complexities that confused me. Note: In the typeout message, \space should appear after \string\foo. I corrected my question. No matter, since it will actually be replaced by a package error message. Also, it does not matter if the message repeats (being an error) because the custom error message will specifically instruct the user to stop. – rallg Feb 08 '24 at 15:40
  • 1
    @cfr This solution is just wrong. Try adding \usepackage{lipsum} to the preamble and change the document body to \begin{document} Welcome.\par \foo No problem here.\par \unfoo \foo \lipsum[1-5]\par \unfoo \end{document}. No error will be seen. – user202729 Feb 08 '24 at 21:12
  • @user202729 OK, but lipsum (and most packages) cannot be used in my document class. But for the benefit of others who come here (with their own situations), your comment is valuable. – rallg Feb 08 '24 at 21:51
  • @rallg No, that's not the point, lipsum is just used to generate dummy text. If the user copy-paste the same text that lipsum produces into the document body itself, then the problem remains the same. – user202729 Feb 08 '24 at 21:54
  • @user202729 I just did what you said in the above comment, and the cfr solution worked for me. Compiled lipsum in article, selected all text (2 pages) from the PDF in Evince Linux, copied and pasted into my own document class (not article), ended paragraphs with \par, and the message appeared as expected. Probably has something to do with my non-standard document class, which cheerfully tosses all math and many other LaTeX commands. – rallg Feb 08 '24 at 22:06
  • @user202729 It won't work, I think, when \unfoo occurs soon after the page break. I guess this is to be expected since LaTeX will likely have typeset \unfoo already in that case. The use of \par is irrelevant: you can demonstrate the same issue with ordinary paragraph breaks. – cfr Feb 09 '24 at 04:15
  • @rallg I suspect that may be because the pages are broken at different points. As I understand it, more material than fills a page will be typeset at ship out whenever TeX's line-breaking/page-breaking algorithm is responsible for the break. Unless your class isn't using that rather fundamental feature of TeX, it will be vulnerable to the same problem. Tossing maths and LaTeX commands makes no odds: you could reproduce the same effect without using LaTeX at all. (Though you'd need to emulate the ship out hook, of course.) – cfr Feb 09 '24 at 04:21
  • @user202729 The way to avoid this is to use the .aux which the OP doesn't wish to do. At the time the final \unfoo is typeset, the page hasn't yet shipped out, so LaTeX naturally thinks all is well. And this isn't class-specific that I know of. It's just a function of how close \unfoo is to the break: if it is close, it will be typeset prior to shipout. In effect, it is typeset as part of the to-be-previous page. Should I delete this, do you think? – cfr Feb 09 '24 at 04:33
  • @cfr You don't need pdfsavepos stuff, you can use marks. // Well, if it's for sanity checks, it "mostly works" anyway (if the page is way too long you get a complaint) so I think it's fine (and I don't think you can delete accepted answer either) – user202729 Feb 09 '24 at 05:21
  • @cfr I will keep all of this in mind. It turns out that I do not need to detect missing \unfoo at the very first page break. It can wait for another page, as long as the detection is not deferred for very long. There are other ways to deal with this, such as placing the detection in any command that would (in my usage) never appear between \foo and \unfoo. This, I know how to do. – rallg Feb 09 '24 at 16:21
  • 1
    @rallg If it can wait for another page, this should work fine. The case in which it fails is one where \unfoo occurs close to the top of the next page rather than on the current page. Any later than that and you'll generate the error. – cfr Feb 10 '24 at 00:12