我有以下代码
\output={\plainoutput \message{outputpenalty=\the\outputpenalty}} % for debugging
\hrule
\vskip21.9cm plus1pt % plus1pt because to avoid underfull vbox until output
one paragraph
\par
\hrule % \hrule is here only as an "visualizer", you can remove it
\medskip
\end
tex test
此代码在输出中生成(使用命令)两个页面。第一页包含两个\hrule
s,第二页为空。分页符在\medskip
粘合处完成。这意味着此粘合从主垂直列表中丢弃。但\end
原语生成\hbox to\hsize{} \vfill \penalty-1073741824
(参见 TeXbook 第 264 页),这会产生第二个空页。但条件(TeXbook 中提到:“仅当主垂直列表尚未完全输出时才附加此材料”)不成立,恕我直言。所以,我不明白为什么会生成一个空页。
我们可以做这些实验:如果我们\medskip
在粘合处加上前缀\penalty-10000
(别名\break
),那么分页符会以这个惩罚完成,下一个\medskip
粘合会被丢弃,所以我们只有一页。这没问题。但如果我们在粘合\medskip
处加上前缀\penalty-9999
,那么分页符会以这个惩罚完成,下一个可丢弃的粘合不会被丢弃,并且会生成第二个空页。为什么?
请注意,最后一句话并不完全正确。\medskip
胶水是在最终输出中被丢弃(我们仅\hbox to\hsize{}
在日志中看到),但当原语评估其条件(来自 TeXbook,第 264 页)\tracingoutput=1
时,它的行为就像不可丢弃的。\end
答案1
我对这个问题进行了更多的探索,希望我有一个答案。
首先,从 TeXbook 术语做一个简短的总结。
主垂直列表被假想的水平线分成两部分:当前页面在这条线之上,最近的贡献在其下方。当前页面有C在所有可能的断点处计算价值(成本),但最近的贡献等待这样的计算。页面构建器是行使当\par
完成或将框或惩罚添加到主垂直列表时(本例中还有更多不相关的情况)。当仅将胶水附加到主垂直列表时,不会执行此操作。
页面构建器将假想线移到下方,即计算新的C值,即从最近贡献中移动材料到当前页面。当最近贡献为空时,即假想线位于垂直列表的底部时,它会停止。通常(在页面中间)它不会再做任何事情。但是当 c=infty(此时页面过满)或惩罚<=-10000(立即中断)时,它会开始输出过程. 输出过程结束后它继续工作。
输出过程寻找最佳C当前页面中的值。上面的所有内容都将是 box255,它将被移至输出例程并从垂直列表中删除。下面的所有内容都将返回到最近的贡献,因为我们需要重新计算全新的C值。这意味着当输出过程结束时,页面构建器将重新计算这些C所有可能的断点处的值。
现在,我们可以在 OP 中追溯上面的示例。我们可以在原语\showlists
之前立即添加\end
,以显示垂直列表的状态。\medskip
当原语开始执行时,我们可以看到第 1 页(加上)的所有材料都存在于垂直列表中(在当前页面中)\end
。因此,此原语添加了“框胶惩罚”,如 OP 中所述。页面构建器在\end
at \par
(示例中为空行)之前立即启动,但它不会调用输出过程,因为断点 at\medskip
尚未C=infty(不过满)并且 in 之后的断点\medskip
未在列表中显示。
在通过原始方法添加“盒子粘合惩罚”之后,\end
页面构建器被执行,它发现盒子后的粘合如下C=infty(过满)并启动输出过程。最好的C位于\medskip
并创建了第一页。“盒子胶水惩罚”(来自原始\end
)在最近的贡献中再次出现,这导致了第二页。
\penalty-9999
在之前添加时,情况完全相同\medskip
。但\penalty-10000
此处添加 时,情况有所不同。页面构建器立即启动输出过程并\medskip
返回到最近的贡献。创建页面 1 后,页面构建器将其\medskip
视为可丢弃(因为当前页面为空)并忽略它。\end
原语找到空的垂直列表,仅创建页面 1。
\penalty0
如果在后面附加惩罚(例如),会发生什么情况\medskip
?添加惩罚后,页面生成器将运行,因为C=infty 在该惩罚下。然后\medskip
和该惩罚被忽略,因为它们是可丢弃的,并且找到空的垂直列表。当使用宏\end
时,这种行为很典型,因为它将“vfill 惩罚”附加到垂直列表中。\bye
如果删除之前的空行\end
(\par
之前的)会发生什么情况?原语发现主垂直列表非空。此外,由于 之前未立即执行页面构建器,因此将其分为非空当前页面和非空最近贡献。因此,它附加了“盒子粘合惩罚”,并打印了第二个空白页。\end
\end
\end
答案2
主垂直列表分为两部分:当前页面和最近的贡献. 在某些时候,TeX练习页面构建器,即将最近贡献中的项目移动到当前页面:
当将惩罚添加到主垂直列表时(可能来自
\vadjust
);\insert
在;的右括号之后一个框被添加到主垂直列表中(在这种情况下也会
\prevdepth
更新);何时
\par
执行(因此段落被拆分成的框仅调用一次页面构建器);当令牌被交付
$$
后,开始展示时;\everydisplay
当
$$
结束显示时;输出例程结束其工作之后。
当页面构建器运行时,最近贡献列表中的项目将移动到当前页面,并计算分页成本;如果当前页面没有盒子其中,最近贡献列表顶部的可丢弃项被丢弃。TeXbook,第 112 页:
每当 TeX 将项目从“最近贡献”的顶部移动到“当前页面”的底部时,如果当前页面不包含任何框,它会丢弃一个可丢弃的项目(粘连、字距或惩罚)。这就是粘连在分页符处消失的方式。
请注意,Knuth 并没有说“分页符后所有可丢弃的项目都会被丢弃”:在这方面,分页符与换行符有很大不同。
在您的示例中,\par
使 TeX 运行页面构建器;分页的计算成本不是无限的,因此\medskip
将粘合附加到最近的贡献中,然后\end
出现,这增加了相当于
\line{} \vfill \penalty-’10000000000
到主垂直列表(在最近的贡献中)。这些项目由\end
除非并非所有内容都已输出。
在惩罚时,TeX 会执行页面构建器;顶部的粘连不会被丢弃,因为当前页面中有框。但是,这种粘连会使分页的成本无限大,因此移动的内容会放回到最近的贡献中,从粘连开始直到最终作业惩罚,然后页面会被输出。
页面发送出去后,页面生成器再次运行;当前页面为空,因此\medskip
丢弃粘合。\line{}
使 TeX 插入\topskip
粘合,然后为空\hbox
,惩罚结束游戏,因为\end
惩罚后令牌仍然存在。输出另一页。
如果惩罚出现在 之后\medskip
,即使用 时的情况\bye
,则惩罚出现在 之前\end
,因此 TeX 会对其执行页面构建器,粘连就会消失。没有\line{}
添加 ,因为主垂直列表中没有其他内容可输出。
答案3
已经给出了两个很好的答案,详细解释了发生了什么以及为什么:我只想补充几点评论,并用来自适当的跟踪命令的证据来证实分析。
我将使用以下诊断程序:
% !TEX TS-program = pdftex
\showboxbreadth = 1000
\showboxdepth = 10
\output={%
\tracingcommands = 0
\immediate\write -1 {Entering output routine,
\string\outputpenalty\space = \number\outputpenalty.}%
\showlists % output
\plainoutput
\immediate\write -1 {Exiting output routine.}%
}
\tracingpages = 1
\hrule
\vskip 21.9cm plus 1pt \relax % as it was in the question
one paragraph
\par
\hrule % \hrule is here only as a "visualizer", you can remove it
\tracingcommands = 1
\medskip
\showlists % #1
\par % let's make it explicit
\showlists % #2
\end
注意使用\tracingpages = 1
:这将在 TeX 将某个项目(合法断点)从“最近贡献”列表移动到“当前页面”的确切时刻发出一条跟踪线,并计算切断页面的成本多于它正在移动的物品;这条追踪线显示了如何计算这个成本。
我现在将浏览它生成的记录文件并添加我的评论;邀请您编译上述代码,打开记录文件,并查看您的副本以关注即将进行的讨论。
This is pdfTeX, Version 3.14159265-2.6-1.40.16 (TeX Live 2015) (preloaded format=pdftex 2016.3.5) 20 APR 2016 11:35
entering extended mode
restricted \write18 enabled.
file:line:error style messages enabled.
%&-line parsing enabled.
**wipet.tex
(./wipet.tex
%% goal height=643.20255, max depth=4.0
% t=10.0 g=643.20255 b=10000 p=0 c=100000#
最后一行是在 TeX 从“贡献列表”移动到“当前页面”时发出的,这是来自“big”命令的粘合\vskip 29.1cm ...
,这是一个合法的断点:TeX 现在正在考虑切断页面多于这个粘连,也就是在粘连和顶部规则之后。这当然会产生一个无限坏的页面,因此计算了\topskip
成本(最后的标记表明,尽管如此,这是迄今为止出现的最佳断点)。c=100000#
#
以下两行来自我们追踪每个原始命令的执行情况的要求(\tracingcommands = 1
):
{vertical mode: \vskip}
{\showlists}
因此我们看到\medskip
命令被执行了,但是,正如 @egreg 和 @wipet 已经解释的那样,这仅仅是将一个粘合项附加到“最近贡献”列表中,没有执行页面构建器。随后的命令(源代码中\showlists
标记的命令)产生的跟踪证实了这一点:% #1
### vertical mode entered at line 0
### current page:
\glue(\topskip) 9.6
\rule(0.4+0.0)x*
\glue 623.11517 plus 1.0
\glue(\parskip) 0.0 plus 1.0
\hbox(6.94444+1.94444)x469.75499, glue set 386.92151fil
.\hbox(0.0+0.0)x20.0
.\tenrm o
.\tenrm n
.\tenrm e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm p
.\tenrm a
.\tenrm r
.\tenrm a
.\tenrm g
.\tenrm r
.\tenrm a
.\tenrm p
.\tenrm h
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
total height 640.05962 plus 2.0
goal height 643.20255
### recent contributions:
\rule(0.4+0.0)x*
\glue 6.0 plus 2.0 minus 2.0
prevdepth ignored, prevgraf 1 line
./wipet.tex:21: OK.
l.21 \showlists
% #1
?
请注意,尚未发出任何其他行\tracingpages
,但尽管如此,页面的“总高度”已经包括“大”\vskip 29.1cm ...
和文本行的高度,但不包括深度。不要对此感到困惑:TeX 仍然记住“迄今为止最好的断点”多于大\vskip
,因为页面上当前后面的项目都不是合法的断点(粘连\parskip
不是,因为它前面有一个可丢弃的项目)。如果现在在该点发生分页,则后续项目将移回“最近贡献”列表。
然后\par
命令被执行:
{\par}
这个做执行页面构建器,实际上它后面紧接着发出一行\tracingpages
:
% t=642.40405 plus 2.0 g=643.20255 b=6 p=0 c=6#
TeX 刚刚将规则和粘合点从“最近的贡献”移到了当前页面(实际上,触发上述代码的是粘合点,这是一个合法的断点),现在它正在评估切断页面的成本多于这个成本\medskip
很有吸引力,但 TeX 仍在等待是否有更好的方案出现(坏处还不是无限的,惩罚也不低于或等于 -10000)。
接下来是第二条\showlists
命令(标记为% #2
)
{\showlists}
其输出确认\medskip
已被纳入“当前页面”:
### vertical mode entered at line 0
### current page:
\glue(\topskip) 9.6
\rule(0.4+0.0)x*
\glue 623.11517 plus 1.0
\glue(\parskip) 0.0 plus 1.0
\hbox(6.94444+1.94444)x469.75499, glue set 386.92151fil
.\hbox(0.0+0.0)x20.0
.\tenrm o
.\tenrm n
.\tenrm e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm p
.\tenrm a
.\tenrm r
.\tenrm a
.\tenrm g
.\tenrm r
.\tenrm a
.\tenrm p
.\tenrm h
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
\rule(0.4+0.0)x*
\glue 6.0 plus 2.0 minus 2.0
total height 648.40405 plus 4.0 minus 2.0
goal height 643.20255
prevdepth ignored, prevgraf 1 line
./wipet.tex:23: OK.
l.23 \showlists
% #2
?
但是,请再次回想一下,虽然“当前页面”的“总高度”现在包括“大” \vskip 29.1cm ...
、文本行(高度和深度)、底部规则和粘合\medskip
,但“最容易记住的断点”仍然是多于\medskip
(其中一行)t=642.40405 plus 2.0 ...
:正如我们所见,这将是选定的断点,并且将\glue 6.0 plus 2.0 minus 2.0
返回到“最近的贡献”。
\end
那么,当命令被消化时,情况是这样的:
{\end}
由于主要垂直不是已完全输出(参见TeXbook,第 264 页,第一段),此命令插入相当于
\line{} \vfill \penalty-’10000000000
进入主垂直列表——更准确地说,进入“最近的贡献”;由...制作的框\line{}
和...\penalty
练习,导致框、无限胶水和惩罚本身立即转发到“当前页面”;胶水是一个合法的断点,因此 TeX 计算切断页面的成本多于它,即紧接着空框之后,并发出以下跟踪线(始终来自\tracingpages
):
% t=648.40405 plus 4.0 minus 2.0 g=643.20255 b=* p=0 c=*
这次坏处是无限的,因此调用输出例程(还请注意,该行说p=0
,从中可以看出正在考虑的断点确实是粘合剂,不是高度负面的惩罚;但页面在最容易记住的断点处被破坏,让我们再重复一次,它仍然在 之上\medskip
,在t=642.40405 plus 2.0
处c=6
,最后一个计算用 符号标记#
)。实际上,紧接着的是我们在自定义输出例程中包含的命令所产生的跟踪:
{internal vertical mode: \tracingcommands}
Entering output routine, \outputpenalty = 10000.
(请注意,\outputpenalty
具有“粘合处”中断的预期值)。这些命令中包括\showlists
标有 的命令% output
:
### internal vertical mode entered at line 24 (\output routine)
prevdepth ignored
### vertical mode entered at line 0
### recent contributions:
\glue 6.0 plus 2.0 minus 2.0
\hbox(0.0+0.0)x469.75499
\glue 0.0 plus 1.0fill
\penalty -1073741824
prevdepth ignored, prevgraf 1 line
./wipet.tex:24: OK.
<output> ...= \number \outputpenalty .}\showlists
\plainoutput \immediate \w...
<to be read again>
\end
l.24 \end
?
再次,一切都如我们所期望的那样:输出例程构建的内部垂直列表显然仍然为空,主垂直列表包含所选断点之后的项目,从所选断点本身开始,这些项目已返回到“最近的贡献”。
因此第一页被弹出……
[1{/usr/local/texlive/2015/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
...然后输出例程终止。
Exiting output routine.
当输出例程结束时,页面生成器会立即再次执行。当前页面是空的,特别是它不包含任何框,因此被\glue 6.0 ...
丢弃。然后是空框:由于这是第一个贡献给页面的框,TeX 在其上方插入\topskip
粘连并发出行
%% goal height=643.20255, max depth=4.0
注意\topskip
胶水不是合法的断点(因为它是不是前面有一个不可丢弃的项目);实际上,当前页面还没有任何合法的断点。第一个是无限胶水,当它将其移动到“当前页面”列表时,TeX 发出以下行
% t=10.0 g=643.20255 b=10000 p=0 c=100000#
这相当于破坏页面的成本多于\penalty-’10000000000
无限胶水。但所选断点将是 TeX在将“最近的贡献”移动到“当前页面”时考虑的下一个断点:
% t=10.0 plus 1.0fill g=643.20255 b=0 p=-1073741824 c=-1073741824#
(可以说“高于”惩罚)。这里 p≤-10000,因此立即调用输出例程:
{\tracingcommands}
Entering output routine, \outputpenalty = -1073741824.
(注意 的值\outputpenalty
)。列表的状态现在如下:
### internal vertical mode entered at line 24 (\output routine)
prevdepth ignored
### vertical mode entered at line 0
### recent contributions:
\penalty 10000
prevdepth ignored, prevgraf 1 line
./wipet.tex:24: OK.
<output> ...= \number \outputpenalty .}\showlists
\plainoutput \immediate \w...
<to be read again>
\end
l.24 \end
?
您可以再次看到,所选断点已返回到“最近贡献”,但惩罚值更改为 10000(TeXbook,第 125 页)。输出例程终止,弹出第二页……
[2]
Exiting output routine.
…又一个转折开始了:页面生成器被执行,因为输出例程刚刚结束,惩罚项被丢弃,因为当前页面是空的。此后,命令\end
再次执行(第 264 页)…
{vertical mode: \end}
…但这次主垂直列表是空的,因此出现了一个“圆满结局”:
)</usr/local/texlive/2015/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb>
Output written on wipet.pdf (2 pages, 12562 bytes).
PDF statistics:
15 PDF objects out of 1000 (max. 8388607)
9 compressed objects within 1 object stream
0 named destinations out of 1000 (max. 500000)
1 words of extra memory for PDF output out of 10000 (max. 10000000)
(深呼吸。)
也许现在有人会自愿为另外两种情景撰写类似的分析……