Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Can I test whether calling the \end primitive will really end the run?

The naive \ifdim\pagegoal=16383.99998pt yes\else no\fi is not sufficient (even adding to that mix \deadcycles). According to Victor Eijkhout's TeX by Topic, section 27.2:

The \end command --- which is only allowed in external vertical mode --- terminates a TeX job, but only if the main vertical list is empty and \deadcycles=0.

Unfortunately, \pagegoal can be 16383.99998pt even with a non-empty main vertical list: the following produces one page of output.

\write16{}
\message{\the\pagegoal}
\end

There may be another way to do what I am trying to do. The context is that I have full control on every token that is sent to TeX (parsing TeX in TeX --- don't tell me I shouldn't), and I need to do some action after the last page is output (namely, write </body></html> to an file), in a reliable way. Doing it when the \end primitive is sensed may not be late enough.

share|improve this question
    
I'm curious, actually, why you are parsing TeX in TeX. I've seen your questions on aspects of this topic come up from time to time over about the last year, and you've always alluded to this goal but never explained it. –  Ryan Reich Dec 25 '11 at 16:11
6  
@RyanReich: conversion from a TeX file such as "Hello World.\bye" to html etc instead of dvi or pdf. tex4ht post-processes a dvi with many \special. Andrew Stacey's approach is to post-process the pdf. I'd like to directly produce a text file. Three options: write a new engine (I'm not ready for that); within TeX make every character active writing itself to a file --- but that's not robust at all; or leave all definitions unchanged but expand macros and catch primitives as they arrive into TeX's stomach (my current approach). Getting all the interactions right is obviously tough. –  Bruno Le Floch Dec 25 '11 at 16:35
    
Neat! That's exactly the way I would want to write such a converter too. –  Ryan Reich Dec 25 '11 at 19:31
add comment

2 Answers

Yes we can! :-) (assuming I understand the question correctly)

But only in a roundabout way and only if you have full control over the invoked output routine(s). Basically for \end to end the TeX job it requires that the main vertical list is empty and \deadcycles has a value of 0.

Now unless you keep track of everything that gets normally internally managed by TeX it is impossible to determine from within the main document if \end will end the run. You can look at \pagegoal but all that tells you is if the current page contains any boxes but that doesn't account for other nodes such as "writes" and I think "specials".

But in the opposite direction you could make sure that the page not empty when \end is executed, e.g., though a sequence like

\endgraf\vbox{}\end

so that you definitely get an output routine call and then you can check for the fact that this was triggered by \end because of the \outputpenalty being -1073741824. At that point you will have all material in box 255 followed by your vbox.

Now what this doesn't account for is heldovers from insertions or footnotes that might get split. If that is a problem then a more elaborate scheme like one used by LaTeX for \clearpage. The basic algorithm is the following:

  • end the current page (via \newpage)
  • followed by \vbox{}\penalty -10001 to signal that we want to do a clear page operation
  • then the OR is outputting floats and looks at the footnote insertions
  • and repeatedly issues further \clearpage commands until all floats and fottnotes are typeset
  • only then it ends this cycle.

(the whole sequence is a little bit more complicated because of a possible two-column complexity but ...)

So what LaTeX really does is running more or less \clearpage\end which means that by the time the \endis encountered everything that should be typeset has been typeset already.

Plain TeX works similar but uses an output routine that simply looks at \insertpenalties > 0 to decide if it needs to loop. That is not possible with LaTeX as the floats are not real insertions but basically used as boxes only.

share|improve this answer
    
I don't have full control over the \output routine, because I'd like my code to work regardless of what packages the end-user has put in. However, I might be able to (1) save deadcycles (2) in a group, set the output routine to something trivial (3) do what you describe to know whether the page was empty or not, I still need time to grok completely how (4) put the material back on the MVL (5) close the group, now equipped with the relevant info. Does that seem feasible? –  Bruno Le Floch Jan 9 '12 at 17:58
    
@Bruno I would probably simply hook into \@@end in that case. Of course this should not be done by a normal LaTeX package but for this kind of extreme case it might be the simplest solution. –  Frank Mittelbach Jan 9 '12 at 23:03
    
Redefining \end here is pointless: I already know exactly when it is going to be called. The main question is to know at a given time if the page is empty. I have working code now, thanks to your answer. (1) Examine \pagegoal and \deadcycles: those detect all cases except a lone \special or \write command. (2) \begingroup\output{\aftergroup\endgroup<tests on \box255>} followed by \hbox{}\vfill\penalty-10000 to force an \output. (3) The test is \setbox0\lastbox\ifvoid0 after \vbox{}\unvcopy255: 0 is void if box 255 was non-empty, otherwise is the vbox. –  Bruno Le Floch Jan 10 '12 at 6:06
add comment
up vote 7 down vote accepted

Thanks to Frank's suggestions, I solved my problem.

The task of \theend is to do what the \end primitive would do (call the output routine until the page is empty and \deadcycles is zero), and then prints The end, ending immediately by a call to the primitive \end.

The main idea is to temporarily redefine \output and force a call to it, allowing me to check what is in the current page. If it is empty, I'm really done, and I can close my html file safely. Otherwise, I put the material back in the page, and I make sure to restore the original \output before forcing a call to it with what \end would insert, namely \hbox{}\vfill\penalty-1073741824. To be made robust, the solution should add tests for which mode we are in and add \par in horizontal mode for instance.

% Helpers:
%
\long\def\T#1#2{#1}
\long\def\F#1#2{#2}
%
% Firstly, the output routine should be called if either
% |\deadcycles| is non-zero or |\pagegoal| is not |\maxdimen|
% (meaning that there is a something in the main vertical list).
% This is acheived by inserting what |\end| would insert
% (see \TEforceoutput), then calling |\theend| again.
%
\def\theend
  {%
    \ifdim\pagegoal=\maxdimen
      \ifnum 0=\deadcycles
        \expandafter\expandafter\expandafter\T
      \else \expandafter\expandafter\expandafter\F
      \fi
    \else \expandafter\F
    \fi
    {\TEtest}%
    {\TEforceoutput\theend}%
  }
\def\TEforceoutput{\hbox{}\vfill\penalty-1073741824 }
%
% If both \pagegoal=\maxdimen and \deadcycles=0, there is no box
% in the page, but we still need to test whether the page was really
% empty. For that, set the \output routine to a test, and force a call
% to it using |\TEforceoutput|.
%
% The test resets |\deadcycles| (because we may call the true |\output|
% routine afterwards), then removes from |\box255| the spurious material
% which we had added to force a call to \output. If the resulting page
% is empty, then we reached the true end of the run. Otherwise, after
% the test |\output| routine has ended, we must force the true |\output|
% to take place, then go all the way back to |\theend|.
%
% Of course, don't forget to place the page's contents back into the main
% vertical list, so that potential |\special| and |\write| are not lost.
%
\def\TEtest
  {%
    \begingroup\output{\aftergroup\endgroup\TEoutput}%
    \TEforceoutput
  }
\def\TEoutput
  {%
    \deadcycles=0
    \TEcleanpage
    \TEifpageempty
      {\immediate\write16{** The end! **}\aftergroup\end}%
      {\aftergroup\TEforceoutput\aftergroup\theend}%
    \unvbox255
  }
%
% How to ``clean'' the page from the material that was added to
% force an |\output|: remove the skips and boxes.
%
\def\TEcleanpage
  {%
    \setbox255\vbox
      {%
        \unvbox255
        \unskip % remove \vfill
        \setbox0\lastbox % remove empty \hbox{}
        \unskip % remove \topskip
      }%
  }
%
% We have tested that the page contains no box. If it contains
% other material, then the last item in |\vbox{}\unvcopy255|
% is not a box, hence |\lastbox| is void.
%
\def\TEifpageempty%
  {%
    \setbox0\vbox
      {%
        \vbox{}\unvcopy255\setbox0\lastbox
        \expandafter
      }%
    \ifvoid0 \expandafter\F\else\expandafter\T\fi
  }
%
% Try un-commenting the next line.
\write16{** Hi **}
\theend
share|improve this answer
    
Ah, it seems \prevdepth should be set to -1000pt also to make sure the conditions are exactly the same when the original output routine is called. –  Bruno Le Floch Jan 12 '12 at 16:49
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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