我可以测试调用\end
原语是否真的会结束运行吗?
单纯\ifdim\pagegoal=16383.99998pt yes\else no\fi
是不够的(甚至还加上了这种混合\deadcycles
)。根据 Victor Eijkhout 的TeX 按主题分类,第 27.2 节:
该
\end
命令 --- 只允许在外部垂直模式下使用 --- 终止 TeX 作业,但前提是主垂直列表为空且\deadcycles=0
。
不幸的是,\pagegoal
即使主垂直列表非空,也可能是 16383.99998pt:以下产生一页输出。
\write16{}
\message{\the\pagegoal}
\end
可能还有其他方法可以完成我想做的事情。上下文是,我可以完全控制发送到 TeX 的每个标记(在 TeX 中解析 TeX --- 不要告诉我我不应该这样做),并且我需要在输出最后一页后</body></html>
以可靠的方式执行某些操作(即写入文件)。在\end
感知到原语时执行此操作可能还不够晚。
答案1
是的,我们可以!:-)(假设我正确理解了这个问题)
但只能以迂回的方式,并且只有当您完全控制调用的输出例程时才可以。基本上,要\end
结束 TeX 作业,需要主垂直列表为空且\deadcycles
值为 0。
现在,除非您跟踪 TeX 内部管理的所有内容,否则无法从主文档中确定是否\end
会结束运行。您可以查看,\pagegoal
但所有信息都告诉您当前页面是否包含任何框,但这并不考虑其他节点,例如“写入”和我认为的“特殊”。
但在相反的方向上,你可以确保页面在\end
执行时不为空,例如,尽管像这样的序列
\endgraf\vbox{}\end
这样您肯定会得到一个输出例程调用,然后您可以检查这是由 触发\end
的\outputpenalty
。-1073741824
此时,您将在框 255 中拥有所有材料,然后是您的 vbox。
现在,这没有考虑到插入或脚注的遗留问题,这些遗留问题可能会被拆分。如果这是一个问题,那么可以使用更复杂的方案,例如 LaTeX 使用的方案\clearpage
。基本算法如下:
- 结束当前页面(通过
\newpage
) - 随后
\vbox{}\penalty -10001
表示我们要执行清除页面操作 - 然后 OR 输出浮点数并查看脚注插入
- 并反复发出进一步的
\clearpage
命令,直到所有浮点数和注记都排版完毕 - 只有这样,这个循环才会结束。
(由于可能有两列复杂性,整个序列会稍微复杂一些,但是......)
因此,LaTeX 实际所做的就是或多或少地运行,\clearpage\end
这意味着当\end
遇到时,所有应该排版的内容都已经排版好了。
Plain TeX 的工作原理类似,但使用输出例程来\insertpenalties > 0
决定是否需要循环。这在 LaTeX 中是不可能的,因为浮点数不是真正的插入,而基本上只是用作框。
答案2
感谢弗兰克的建议,我解决了我的问题。
的任务\theend
是执行\end
原语要做的事情(调用输出例程,直到页面为空且
\deadcycles
为零),然后打印结束,通过调用原始的立即结束\end
。
主要思想是临时重新定义\output
并强制调用它,这样我就可以检查当前页面中的内容。如果它是空的,我就真的完成了,我可以安全地关闭我的 html 文件。否则,我将材料放回页面中,并确保在使用要插入的\output
内容强制调用它之前恢复原始内容\end
,即\hbox{}\vfill\penalty-1073741824
。为了使其更强大,解决方案应该添加测试以测试我们处于哪种模式,\par
例如添加水平模式。
% 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