提问于:
浏览数:
2938
## 编译环境
操作系统
* [x] Windows 7/8/10
* [ ] macOS
* [ ] Linux
`若需勾选,请把[ ]改成[x]`
Tex发行版
* [x] TexLive `年份`
* [ ] MikTeX `版本号`
* [ ] CTeX
`若需勾选,请把[ ]改成[x]`
## 背景
之前看完《[The TeX Book](https://wenda.latexstudio.net/data/ueditor/php/upload/file/20200719/1595134441926322.pdf)》,做了一个关于页面钩子的练习——《[不使用atbegshi宏包向\shipout添加钩子的方法研究](https://wenda.latexstudio.net/q-2411.html)》。现在又看了《[TeX by Topic](https://wenda.latexstudio.net/data/ueditor/php/upload/file/20200719/1595135893387660.pdf)》的前五章,终于学会做段落钩子和行钩子了!
plain TeX 原理的 LaTeX 实现。
## 页面钩子
先来把页面钩子复习一下
```tex
%为输出页面命令换个名字
\let\shipout@temp=\shipout
%先挂一个输出钩子
\def\shipout{\shipout@hook\shipout@temp}
%在输出钩子中先解包再封包,并一前一后挂两个页面钩子
\def\shipout@hook{%
\setbox\@outputbox=\vbox to\pagegoal{\pageboxfronthook\unvbox\@outputbox\pageboxbackhook}}
%最后自由实现两个页面钩子
\def\pageboxfronthook{PAGE FRONT}
\def\pageboxbackhook{PAGE BACK}
```
按照《[不使用atbegshi宏包向\shipout添加钩子的方法研究](https://wenda.latexstudio.net/q-2411.html)》中的练习,我应该将页后钩子改成
```tex
\def\pageboxbackhook{PAGE BACK\ifdim\pagetotal<.5\pagegoal\vfill\hbox to\hsize{\hfill NULL\hfill}\vfill\fi}
```
## 段落钩子
根据《[TeX by Topic](https://wenda.latexstudio.net/data/ueditor/php/upload/file/20200719/1595135893387660.pdf)》的 5.6 节
> This mechanism can be used to scoop up paragraphs:
> ```tex
> \everypar{\setbox\parbox=
> \vbox\bgroup
> \everypar{}
> \def\par{\egroup\UseBox\parbox}}
> ```
但是在 [中文版](https://wenda.latexstudio.net/data/ueditor/php/upload/file/20200719/1595136044988601.pdf) 的译注上指出“这里的 `\parbox` 不是 LaTeX 里的段落盒子”。那我们换个自定义的盒子来装段落内容不就好了呗~~
因此自己 new 一个段落盒子
```tex
\newbox\@parbox
\everypar{\setbox\@parbox=
\vbox\bgroup
\everypar{}
\def\par{\egroup\UseBox\@parbox}}
```
在 `texdoc source2e` 中指出 `\everypar` 命令被重载成局部的了。因此将 `\everypar` 命令放在导言区将不工作,为此我们应该将它放在 `\begin{document}` 之后。而在此,我将把他放在 `\AtBeginDocument` 钩子中。加上一些段落钩子,就变成下面这样了
```tex
\newbox\@parbox
\AtBeginDocument{%
\everypar{%
\setbox\@parbox=\vbox\bgroup%或者\vtop
\everypar{}
\def\par{\parboxinnerhook\@par\egroup\parboxfronthook
\box\@parbox\parboxbackhook\@par}}}
%段落钩子可以自己随便定义啦,例如
\def\parboxfronthook{\kern-\parindent\llap{\texttt{PAR FRONT}}}
\def\parboxinnerhook{\texttt{PAR INNER}}
\def\parboxbackhook{\rlap{\texttt{PAR BACK}}}
```
## 行钩子
书上又接着说了
> In this example, the `\UseBox` command can only treat the box as a whole; if the elements of the box should somehow be treated separately another approach is necessary. In
> ```tex
> \everypar{\setbox\parbox=
> \vbox\bgroup\everypar{}%
> \def\par{\endgraf\HandleLines
> \egroup\box\parbox}}
> \def\HandleLines{ ... \lastbox ... }
> ```
the macro `\HandleLines` can have access to successive elements from the vertical list of
the paragraph. See also the example on page 71 (5.9.6 小节).
*注:在 plain TeX 中 `\par` 是此处 `\endgraf` 的别称,虽然在 LaTeX 中也可使用,但是 LaTeX2e 中 `\par` 被定义为 `\@par` 的别称。为了直观起见,我习惯使用 `\@par` 。*
随后翻到 5.9.6 小节,给出了一个解析行的递归宏。我逐行解释一下
```tex
\newbox\linebox \newbox\snapbox%初始化两个box寄存器
\def\eatlines{%
\setbox\linebox\lastbox%取最后一行
\ifvoid\linebox\else%如果最后一行非空
\unskip\unpenalty%去掉最后一行的粘连和惩罚
{\eatlines}%递归读取下一个最后一行(即倒数第二行)
%放在组括号内是为了不会因共用\linebox和\snapbox寄存器,而使前一行内容覆盖掉后一行内容。
%若没在组内,那么由于递归结束的条件是最后一行为空。因此第3行会把空的\lastbox赋值给\linebox,
%使得下面所有的\linebox和\snapbox全空。
\setbox\snapbox\hbox{\unhcopy\linebox}%重组盒子
\ifdim\wd\snapbox<.98\wd\linebox%利用重组盒子和原生盒子的差异,选择性输出盒子
\box\snapbox
\else
\box\linebox
\fi
\fi}
```
那么我们只要向 `\eatlines` 中加上一些钩子,并且修改一下输出盒子的部分代码即可:
```tex
\newbox\linebox
\def\eatlines{
\setbox\linebox\lastbox
\ifvoid\linebox\else
\unskip\unpenalty
{\eatlines}%
\hbox{\lineboxfronthook\box\linebox\lineboxendhook}%
\fi}
%钩子照旧随便自定义
\def\lineboxfronthook{\texttt{>>}}
\def\lineboxendhook{\texttt{<<}}
```
别忘了将 `\eatlines` 整合到上面段落钩子的程序中。
```tex
\AtBeginDocument{%
\everypar{%
\setbox\@parbox=\vbox\bgroup%或者\vtop
\everypar{}%
\def\par{\parboxinnerhook\@par\eatlines\egroup\parboxfronthook
\box\@parbox\parboxbackhook\@par}}}
```
## MWE1
附件见 [demo1.tex](https://wenda.latexstudio.net/data/ueditor/php/upload/file/20200719/1595144762540390.txt) [demo1.pdf](https://wenda.latexstudio.net/data/ueditor/php/upload/file/20200719/1595144400942471.pdf)
```tex
\documentclass{article}
% \usepackage{lipsum}
\makeatletter
\let\shipout@temp=\shipout
\def\shipout{\shipout@hook\shipout@temp}
\def\shipout@hook{%
\setbox\@outputbox=\vbox to\pagegoal{\pageboxfronthook\unvbox\@outputbox\pageboxbackhook}}
\newbox\linebox
\def\eatlines{%
\setbox\linebox\lastbox
\ifvoid\linebox\else
\unskip\unpenalty
{\eatlines}%
\hbox{\lineboxfronthook\box\linebox\lineboxendhook}%
\fi}
\newbox\@parbox
\AtBeginDocument{%
\everypar{%
\setbox\@parbox=\vbox\bgroup%或者\vtop
\everypar{}%
\def\par{\parboxinnerhook\@par\eatlines\egroup\parboxfronthook
\box\@parbox\parboxbackhook\@par}}}
\def\parboxfronthook{\kern-\parindent\llap{\texttt{PAR FRONT}}}
\def\parboxinnerhook{\texttt{PAR INNER}}
\def\parboxbackhook{\rlap{\texttt{PAR BACK}}}
\def\lineboxfronthook{\texttt{>>}}
\def\lineboxendhook{\texttt{<<}}
\def\pageboxfronthook{PAGE FRONT}
\def\pageboxbackhook{PAGE BACK\ifdim\pagetotal<.5\pagegoal\vfill\hbox to\hsize{\hfill NULL\hfill}\vfill\fi}
\makeatother
\begin{document}
% \lipsum
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut purus elit, vestibulum ut, placerat ac, adipiscing vitae, felis. Curabitur dictum gravida mauris. Nam arcu libero, nonummy eget, consectetuer id, vulputate a, magna. Donec vehicula augue eu neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris ut leo. Cras viverra metus rhoncus sem. Nulla et lectus vestibulum urna fringilla ultrices. Phasellus eu tellus sit amet tortor gravida placerat. Integer sapien est, iaculis in, pretium quis, viverra ac, nunc. Praesent eget sem vel leo ultrices bibendum. Aenean faucibus. Morbi dolor nulla, malesuada eu, pulvinar at, mollis ac, nulla. Curabitur auctor semper nulla. Donec varius orci eget risus. Duis nibh mi, congue eu, accumsan eleifend, sagittis quis, diam. Duis eget orci sit amet orci dignissim rutrum.
Nam dui ligula, fringilla a, euismod sodales, sollicitudin vel, wisi. Morbi auctor lorem non justo. Nam lacus libero, pretium at, lobortis vitae, ultricies et, tellus. Donec aliquet, tortor sed accumsan bibendum, erat ligula aliquet magna, vitae ornare odio metus a mi. Morbi ac orci et nisl hendrerit mollis. Suspendisse ut massa. Cras nec ante. Pellentesque a nulla. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam tincidunt urna. Nulla ullamcorper vestibulum turpis. Pellentesque cursus luctus mauris.
Nulla malesuada porttitor diam. Donec felis erat, congue non, volutpat at, tincidunt tristique, libero. Vivamus viverra fermentum felis. Donec nonummy pellentesque ante. Phasellus adipiscing semper elit. Proin fermentum massa ac quam. Sed diam turpis, molestie vitae, placerat a, molestie nec, leo. Maecenas lacinia. Nam ipsum ligula, eleifend at, accumsan nec, suscipit a, ipsum. Morbi blandit ligula feugiat magna. Nunc eleifend consequat lorem. Sed lacinia nulla vitae enim. Pellentesque tincidunt purus vel magna. Integer non enim. Praesent euismod nunc eu purus. Donec bibendum quam in tellus. Nullam cursus pulvinar lectus. Donec et mi. Nam vulputate metus eu enim. Vestibulum pellentesque felis eu massa.
Quisque ullamcorper placerat ipsum. Cras nibh. Morbi vel justo vitae lacus tincidunt ultrices. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. In hac habitasse platea dictumst. Integer tempus convallis augue. Etiam facilisis. Nunc elementum fermentum wisi. Aenean placerat. Ut imperdiet, enim sed gravida sollicitudin, felis odio placerat quam, ac pulvinar elit purus eget enim. Nunc vitae tortor. Proin tempus nibh sit amet nisl. Vivamus quis tortor vitae risus porta vehicula.
Fusce mauris. Vestibulum luctus nibh at lectus. Sed bibendum, nulla a faucibus semper, leo velit ultricies tellus, ac venenatis arcu wisi vel nisl. Vestibulum diam. Aliquam pellentesque, augue quis sagittis posuere, turpis lacus congue quam, in hendrerit risus eros eget felis. Maecenas eget erat in sapien mattis porttitor. Vestibulum porttitor. Nulla facilisi. Sed a turpis eu lacus commodo facilisis. Morbi fringilla, wisi in dignissim interdum, justo lectus sagittis dui, et vehicula libero dui cursus dui. Mauris tempor ligula sed lacus. Duis cursus enim ut augue. Cras ac magna. Cras nulla. Nulla egestas. Curabitur a leo. Quisque egestas wisi eget nunc. Nam feugiat lacus vel est. Curabitur consectetuer.
Suspendisse vel felis. Ut lorem lorem, interdum eu, tincidunt sit amet, laoreet vitae, arcu. Aenean faucibus pede eu ante. Praesent enim elit, rutrum at, molestie non, nonummy vel, nisl. Ut lectus eros, malesuada sit amet, fermentum eu, sodales cursus, magna. Donec eu purus. Quisque vehicula, urna sed ultricies auctor, pede lorem egestas dui, et convallis elit erat sed nulla. Donec luctus. Curabitur et nunc. Aliquam dolor odio, commodo pretium, ultricies non, pharetra in, velit. Integer arcu est, nonummy in, fermentum faucibus, egestas vel, odio.
Sed commodo posuere pede. Mauris ut est. Ut quis purus. Sed ac odio. Sed vehicula hendrerit sem. Duis non odio. Morbi ut dui. Sed accumsan risus eget odio. In hac habitasse platea dictumst. Pellentesque non elit. Fusce sed justo eu urna porta tincidunt. Mauris felis odio, sollicitudin sed, volutpat a, ornare ac, erat. Morbi quis dolor. Donec pellentesque, erat ac sagittis semper, nunc dui lobortis purus, quis congue purus metus ultricies tellus. Proin et quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Praesent sapien turpis, fermentum vel, eleifend faucibus, vehicula eu, lacus.
\end{document}
```
![](https://wenda.latexstudio.net/data/attach/200719/ANnhSte3.png)
我本来想用 `\lipsum` 的,但是会报错,原因未明,估计是重定义的 `\par` 哪里的 `\egroup` 没处理好吧,反正正式文章中也用不到这样的命令,就不管了。
## 允许 `\@parbox` 内分页算法
出现了一个比较严重的问题是,每一段都被重新放在一个 `\vbox` 里面,导致分页算法在 `\vbox` 内失效了,只能在段间分页,而不能段内分页。
解决方法是在每个段落盒子 `\@parbox` 中判断一下当前页面剩余空白能否装下整个段落,若不能,则拆分盒子强制分页。
```tex
\newbox\parsplit
\newdimen\pageblank
\pageblank=\pagegoal
\advance\pageblank by-\pagetotal
\advance\pageblank by-1pt%我也不明白为什么不减这1pt就会导致分页失败
\ifdim\ht\@parbox>\pageblank
\setbox\parsplit=\vsplit\@parbox to\pageblank%拆分盒子
\vbox{\unvbox\parsplit}%先解包再封包是为了去掉一些包末的粘连
\pagebreakbackhook%分页前的段落后钩子
\break%强制分页
\pagebreakfronthook%分页后的段落前钩子
\fi
```
## MWE2(允许页末段落盒子正常分页)
附件见 [demo2.tex](https://wenda.latexstudio.net/data/ueditor/php/upload/file/20200719/1595144762324123.txt) [demo2.pdf](https://wenda.latexstudio.net/data/ueditor/php/upload/file/20200719/1595144400596093.pdf)
```tex
\documentclass{article}
% \usepackage{lipsum}
\makeatletter
\let\shipout@temp=\shipout
\def\shipout{\shipout@hook\shipout@temp}
\def\shipout@hook{%
\setbox\@outputbox=\vbox to\pagegoal{\pageboxfronthook\unvbox\@outputbox\pageboxbackhook}}
\newbox\linebox
\def\eatlines{%
\setbox\linebox\lastbox
\ifvoid\linebox\else
\unskip\unpenalty
{\eatlines}%
\hbox{\lineboxfronthook\box\linebox\lineboxendhook}%
\fi}
\newbox\@parbox
\newbox\parsplit
\newdimen\pageblank
\AtBeginDocument{%
\everypar{%
\setbox\@parbox=\vbox\bgroup%或者\vtop
\everypar{}%
\def\par{\parboxinnerhook\@par\eatlines\egroup\parboxfronthook
\pageblank=\pagegoal
\advance\pageblank by-\pagetotal
\advance\pageblank by-1pt
\ifdim\ht\@parbox>\pageblank
\setbox\parsplit=\vsplit\@parbox to\pageblank
\vbox{\unvbox\parsplit}%
\pagebreakbackhook
\break
\pagebreakfronthook
\fi
\box\@parbox\parboxbackhook\@par}}}
\def\parboxfronthook{\kern-\parindent\llap{\texttt{PAR FRONT}}}
\def\parboxinnerhook{\texttt{PAR INNER}}
\def\parboxbackhook{\rlap{\texttt{PAR BACK}}}
\def\pagebreakfronthook{\llap{\texttt{PAGE BREAK FRONT}}}
\def\pagebreakbackhook{\rlap{\texttt{PAGE BREAK BACK}}}
\def\lineboxfronthook{\texttt{>>}}
\def\lineboxendhook{\texttt{<<}}
\def\pageboxfronthook{PAGE FRONT}
\def\pageboxbackhook{PAGE BACK\ifdim\pagetotal<.5\pagegoal\vfill\hbox to\hsize{\hfill NULL\hfill}\vfill\fi}
\makeatother
\begin{document}
% \lipsum
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut purus elit, vestibulum ut, placerat ac, adipiscing vitae, felis. Curabitur dictum gravida mauris. Nam arcu libero, nonummy eget, consectetuer id, vulputate a, magna. Donec vehicula augue eu neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris ut leo. Cras viverra metus rhoncus sem. Nulla et lectus vestibulum urna fringilla ultrices. Phasellus eu tellus sit amet tortor gravida placerat. Integer sapien est, iaculis in, pretium quis, viverra ac, nunc. Praesent eget sem vel leo ultrices bibendum. Aenean faucibus. Morbi dolor nulla, malesuada eu, pulvinar at, mollis ac, nulla. Curabitur auctor semper nulla. Donec varius orci eget risus. Duis nibh mi, congue eu, accumsan eleifend, sagittis quis, diam. Duis eget orci sit amet orci dignissim rutrum.
Nam dui ligula, fringilla a, euismod sodales, sollicitudin vel, wisi. Morbi auctor lorem non justo. Nam lacus libero, pretium at, lobortis vitae, ultricies et, tellus. Donec aliquet, tortor sed accumsan bibendum, erat ligula aliquet magna, vitae ornare odio metus a mi. Morbi ac orci et nisl hendrerit mollis. Suspendisse ut massa. Cras nec ante. Pellentesque a nulla. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam tincidunt urna. Nulla ullamcorper vestibulum turpis. Pellentesque cursus luctus mauris.
Nulla malesuada porttitor diam. Donec felis erat, congue non, volutpat at, tincidunt tristique, libero. Vivamus viverra fermentum felis. Donec nonummy pellentesque ante. Phasellus adipiscing semper elit. Proin fermentum massa ac quam. Sed diam turpis, molestie vitae, placerat a, molestie nec, leo. Maecenas lacinia. Nam ipsum ligula, eleifend at, accumsan nec, suscipit a, ipsum. Morbi blandit ligula feugiat magna. Nunc eleifend consequat lorem. Sed lacinia nulla vitae enim. Pellentesque tincidunt purus vel magna. Integer non enim. Praesent euismod nunc eu purus. Donec bibendum quam in tellus. Nullam cursus pulvinar lectus. Donec et mi. Nam vulputate metus eu enim. Vestibulum pellentesque felis eu massa.
Quisque ullamcorper placerat ipsum. Cras nibh. Morbi vel justo vitae lacus tincidunt ultrices. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. In hac habitasse platea dictumst. Integer tempus convallis augue. Etiam facilisis. Nunc elementum fermentum wisi. Aenean placerat. Ut imperdiet, enim sed gravida sollicitudin, felis odio placerat quam, ac pulvinar elit purus eget enim. Nunc vitae tortor. Proin tempus nibh sit amet nisl. Vivamus quis tortor vitae risus porta vehicula.
Fusce mauris. Vestibulum luctus nibh at lectus. Sed bibendum, nulla a faucibus semper, leo velit ultricies tellus, ac venenatis arcu wisi vel nisl. Vestibulum diam. Aliquam pellentesque, augue quis sagittis posuere, turpis lacus congue quam, in hendrerit risus eros eget felis. Maecenas eget erat in sapien mattis porttitor. Vestibulum porttitor. Nulla facilisi. Sed a turpis eu lacus commodo facilisis. Morbi fringilla, wisi in dignissim interdum, justo lectus sagittis dui, et vehicula libero dui cursus dui. Mauris tempor ligula sed lacus. Duis cursus enim ut augue. Cras ac magna. Cras nulla. Nulla egestas. Curabitur a leo. Quisque egestas wisi eget nunc. Nam feugiat lacus vel est. Curabitur consectetuer.
Suspendisse vel felis. Ut lorem lorem, interdum eu, tincidunt sit amet, laoreet vitae, arcu. Aenean faucibus pede eu ante. Praesent enim elit, rutrum at, molestie non, nonummy vel, nisl. Ut lectus eros, malesuada sit amet, fermentum eu, sodales cursus, magna. Donec eu purus. Quisque vehicula, urna sed ultricies auctor, pede lorem egestas dui, et convallis elit erat sed nulla. Donec luctus. Curabitur et nunc. Aliquam dolor odio, commodo pretium, ultricies non, pharetra in, velit. Integer arcu est, nonummy in, fermentum faucibus, egestas vel, odio.
Sed commodo posuere pede. Mauris ut est. Ut quis purus. Sed ac odio. Sed vehicula hendrerit sem. Duis non odio. Morbi ut dui. Sed accumsan risus eget odio. In hac habitasse platea dictumst. Pellentesque non elit. Fusce sed justo eu urna porta tincidunt. Mauris felis odio, sollicitudin sed, volutpat a, ornare ac, erat. Morbi quis dolor. Donec pellentesque, erat ac sagittis semper, nunc dui lobortis purus, quis congue purus metus ultricies tellus. Proin et quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Praesent sapien turpis, fermentum vel, eleifend faucibus, vehicula eu, lacus.
\end{document}
```
![](https://wenda.latexstudio.net/data/attach/200719/eX8L2Jlw.png)
## 问题
1. 改成 `\vtop` 后,段前为什么会多出一行?
2. 我无法修改页面钩子 `\pageboxfronthook` 和 `\pageboxbackhook` 当中的字体,例如将 `NULL` 改为 `\ttfamily\Huge NULL` 这将失效。
3. `\lipsum` 为什么不能用?
4. 我无法使用 `\section` 等标题命令,这跟第 3 个问题是类似的么?
5. 为什么 `\pageblank` 不减一点点(1pt)就会导致分页失败?
本提问源码:[【经验+提问】plain TeX 原理的 LaTeX 实现——页面钩子练习.md](https://wenda.latexstudio.net/data/ueditor/php/upload/file/20200719/1595145870949439.txt)