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
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.