为什么可丢弃的物品不可丢弃?

为什么可丢弃的物品不可丢弃?

我有以下代码

\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此代码在输出中生成(使用命令)两个页面。第一页包含两个\hrules,第二页为空。分页符在\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 中所述。页面构建器在\endat \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练习页面构建器,即将最近贡献中的项目移动到当前页面:

  1. 当将惩罚添加到主垂直列表时(可能来自\vadjust);

  2. \insert在;的右括号之后

  3. 一个框被添加到主垂直列表中(在这种情况下也会\prevdepth更新);

  4. 何时\par执行(因此段落被拆分成的框仅调用一次页面构建器);

  5. 当令牌被交付$$后,开始展示时;\everydisplay

  6. $$结束显示时;

  7. 输出例程结束其工作之后。

当页面构建器运行时,最近贡献列表中的项目将移动到当前页面,并计算分页成本;如果当前页面没有盒子其中,最近贡献列表顶部的可丢弃项被丢弃。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.0c=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)

(深呼吸。)


也许现在有人会自愿为另外两种情景撰写类似的分析……

相关内容