为了激发这个(无可否认)奇怪的问题:
当 TeX 构建垂直列表时,它会跟踪最后一个添加的框的深度,该框被放置在一个特殊变量中,\prevdepth
该变量用于计算段落行的基线跳过粘合。当构建新的垂直列表时(并且 OR 启动新的垂直列表),TeX 会重新初始化\prevdepth
,当处理结束时,它会将其重置为旧的(外部)值。
对于正常的输出例程(即输出页面),这是可以的,因为\prevdepth
(输出例程结束后)如果有剩余的内容未放入 box255,则将匹配主垂直列表中的最后一个框。如果主垂直列表为空,则该值是错误的,但这没有区别,因为 TeX 将\topskip
在第一行之上使用,因此它基本上被忽略了。
但如果输出例程将材料放回去重新处理,情况就不同了。在这种情况下,主垂直列表中的最后一个框是 a) 未在 OR 中使用且具有深度匹配的框,\prevdepth
或者是 OR 贡献的材料中的最后一个框(可能具有不同的深度)。
因此,为了使 baselineskip 计算正确工作,我们需要知道我们处于哪种情况:是否已将所有内容打包到 box255 中,或者是否有未使用的剩余部分?如果可以回答这个问题,那么问题也可以得到解决,但据我所知,这是不可能的。
另一种解决方案是假装 OR 生成的材料具有与外部相同的深度,\prevdepth
但为此我需要以某种方式知道它的值。如果我们用显式惩罚强制 OR,那么知道它的值就很简单,因为这样我就可以保存它并使用保存的值。但是如果 TeX 自己调用 OR,该怎么办?
这实际上是上述问题......有没有什么想法(尽可能狡猾)可以以某种方式在这种特殊情况下获得这个值?
或者,任何方法都可以清楚地确定主垂直列表是否为空。
请注意,我正在寻找一种适用于所有 TeX 引擎的解决方案,因此不会使用 lua 编程。
为了进一步激发灵感,这里有一个简短的纯 TeX 文件,展示了一般问题(尽管这里可以修复,因为我们明确强制输出例程):
\tracingonline=1
\showboxbreadth\maxdimen\showboxdepth\maxdimen
test with g to get a depth
\showthe\prevdepth % we see the prevdepth from the last line
% now assume we have some OR that traps the data and does something with it
% afterwards it is pushing back new or changed data t build pages, In the example I
% simply dropped the collected data and replaced it with soemthing else which has a different
% \prevdepth. A real life example would be rebalancing existing material.
\output{\setbox0=\vbox{\unvbox255}%
next line will be too close\par
\showlists
\showthe\prevdepth
}
\eject
% now we see that the prevdepth should be (and is 0pt) last line jiust contains
% characters without depth
\showlists
\showthe\prevdepth
% but now we got the old \prevdepth back even though it is no longer valid, as
% the OR simply pops the nest even though it is no longer valid.
% As a result we will get the wrong alignment on the next paragraph
Second line \par
\showlists
% here we can see that we are off by the 1.9...pt prevdepth as we should see
% 12pt baseline to baseline but we don't
\output{\plainoutput}
\bigskip
But we really should see:
Next line will not be too close \par Second line
\bye
如果我们运行这个我们会得到:
如果我们查看日志,我们还可以看到错误\prevdepth
是如何弄乱垂直间距的:
> 1.94444pt.
l.6 \showthe\prevdepth
% we see the prevdepth from the last line
?
### internal vertical mode entered at line 18 (\output routine)
\hbox(6.94444+0.0)x469.75499, glue set 337.7548fil
.\hbox(0.0+0.0)x20.0
.\tenrm n
.\tenrm e
.\tenrm x
.\tenrm t
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm l
.\tenrm i
.\tenrm n
.\tenrm e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm w
.\tenrm i
.\tenrm l
.\tenrm l
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm b
.\kern0.27779
.\tenrm e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm t
.\tenrm o
.\kern0.27779
.\tenrm o
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm c
.\tenrm l
.\tenrm o
.\tenrm s
.\tenrm e
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
prevdepth 0.0, prevgraf 1 line
### vertical mode entered at line 0
### recent contributions:
\penalty 10000
prevdepth 1.94444, prevgraf 1 line
! OK.
<output> ...line will be too close\par \showlists
\showthe \prevdepth }
\break ->\penalty -\@M
l.18 \eject
?
> 0.0pt.
<output> ...se\par \showlists \showthe \prevdepth
}
\break ->\penalty -\@M
l.18 \eject
?
### vertical mode entered at line 0
### current page:
\glue(\topskip) 3.05556
\hbox(6.94444+0.0)x469.75499, glue set 337.7548fil
.\hbox(0.0+0.0)x20.0
.\tenrm n
.\tenrm e
.\tenrm x
.\tenrm t
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm l
.\tenrm i
.\tenrm n
.\tenrm e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm w
.\tenrm i
.\tenrm l
.\tenrm l
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm b
.\kern0.27779
.\tenrm e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm t
.\tenrm o
.\kern0.27779
.\tenrm o
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm c
.\tenrm l
.\tenrm o
.\tenrm s
.\tenrm e
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
\penalty 10000
total height 10.0
goal height 643.20255
prevdepth 1.94444, prevgraf 1 line
! OK.
l.23 \showlists
?
> 1.94444pt.
l.24 \showthe\prevdepth
?
### vertical mode entered at line 0
### current page:
\glue(\topskip) 3.05556
\hbox(6.94444+0.0)x469.75499, glue set 337.7548fil
.\hbox(0.0+0.0)x20.0
.\tenrm n
.\tenrm e
.\tenrm x
.\tenrm t
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm l
.\tenrm i
.\tenrm n
.\tenrm e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm w
.\tenrm i
.\tenrm l
.\tenrm l
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm b
.\kern0.27779
.\tenrm e
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm t
.\tenrm o
.\kern0.27779
.\tenrm o
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm c
.\tenrm l
.\tenrm o
.\tenrm s
.\tenrm e
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
\penalty 10000
\glue(\parskip) 0.0 plus 1.0
\glue(\baselineskip) 3.11111
\hbox(6.94444+0.0)x469.75499, glue set 400.31046fil
.\hbox(0.0+0.0)x20.0
.\tenrm S
.\tenrm e
.\tenrm c
.\tenrm o
.\tenrm n
.\tenrm d
.\glue 3.33333 plus 1.66666 minus 1.11111
.\tenrm l
.\tenrm i
.\tenrm n
.\tenrm e
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
total height 20.05556 plus 1.0
goal height 643.20255
prevdepth 0.0, prevgraf 1 line
! OK.
l.32 \showlists
答案1
@wipet 在他的回答中展示了如何解决这个问题,只要我们可以假设\prevdepth
文档中所有材料的深度足够小(\maxdepth
实际上它位于下方)。在这种情况下,我们可以使用框 255 的深度作为计算的度量,\prevdepth
如果最近的贡献中有任何余数,则将进行计算,并使用它来调整新的深度以匹配该深度。如果没有余数,那么这实际上也不重要。事实上,Don Knuth 在 TeXbook 中已经提到了这个技巧,他讨论了一个输出例程,该例程在文本的随机位置添加索引标题(普通文本具有这种很好的特性,其深度小于\maxdepth
...通常但不幸的是并非总是如此。
但是,如果不能保证这一点,这种方法就会失效,而一般情况正是我过去和现在所追求的。他还正确地指出,找出\prevdepth
主垂直列表上的值实际上不会对我们有帮助,所以我上面的问题有部分错误:我们还需要知道最近的贡献中是否确实存在问题,如果存在,则处理它。
所以,在过去的几天里,我一直在想,这是否真的不可能在基本 TeX 中找到(或者它是否需要在 luatex 中进行扩展,或者已经在那里可用并在 ConTeXt 中得到解决……除了你所说的 @Martin 之外,了解它也会很有趣)。就目前而言,在 TeX 中毕竟是可能的。解决方案有点复杂,也许可以进一步简化,但它并不复杂到无法使用(希望不要太复杂以至于我忽略了一些情况)。
主要思想不是尝试直接确定情况,而是连续使用 2 个输出例程,以确保最近的贡献为空。在此过程中,我们收集了足够的信息,以便随后剖析收集到的材料并做我们想做的事情。
我最初的想法是\aftergroup
重新获得控制权并进行查看,但这个想法行不通,因为这样插入的标记在输出例程结束时不会被执行。相反,TeX 立即再次调用“buildpage”程序,该程序会获取最近贡献的任何内容并将其移动到主垂直列表,并且只有发生这种情况后才会查看我插入的标记(换句话说,为时已晚)。
因此,更复杂的方法是,第一个 OR 将 box255 放回原处,但只是将其更改\vsize
为最大可能尺寸。此外,它还用于\aftergroup
让我们稍后再次获得控制权。随着我们进行更改,\vsize
我们将把包括材料近期贡献在内的所有内容放到主垂直列表中,然后我们的控制令牌才会启动。最后,我们将输出例程更改为第二个,然后返回。
插入的令牌\aftergroup
将发出强制惩罚(实际上它会做得更多,见下文),以便抓取所有内容并调用第二个 OR。
在 OR 中,我们现在处于更好的情况:
- 我们知道最近的贡献是空的(除了(
\penalty 10000
) - 我们可以将收集到的东西存放在box255中
- 或者我们可以使用
\vsplit
等来操作它,例如,我们在第一个 OR 中得到的金额分割 - 我们可以使用它
\aftergroup
在手术结束后获得控制权。 - 后者允许我们
\prevdepth
在主垂直列表上进行更改以表示所需的任何内容
我相信这应该可以完全解决问题。
以下是(或多或少有文档化的代码,包括一些测试数据,用于演示各种场景)。它有点长,但主要是因为我试图正确记录最重要的方面和一些更微妙的点。享受:
\tracingoutput1
\showboxbreadth\maxdimen\showboxdepth\maxdimen
\tracingpages1
\tracingonline1
\vsize20\baselineskip
\lineskip=13pt % for identifications
% this is our trial material used below. We will arrange things so
% that the first para will be longer than a page so that we will end
% up with some material on recent contributions. The OR is actually
% then triggered when the first ``p'' is seen. Alternatively one can
% uncomment the \vskip or the \penalty in which case the OR will be
% triggered by them or you could uncomment ``Line2 and3'' then the
% break happens somewhere in the middle of the first paragraph (in
% vmode inn that case)
\def\testmaterial{%
Line 1 \hfil\break
% Line 2 \hfil\break
% Line 3 \hfil\break
\vadjust{\penalty -333 }
some text some text some text some text some text some text
some text some text some text
and some more text ggg \vrule depth 88pt
%\ vskip 17pt
% \penalty 15
pppppppppppppppppppppppppp
\showlists
}
% now this here is just to see the whole stuff being processed by the
% normal OR and see the \showlists result for it. One can then compare
% that to the showlists result we get later to check for differences
\testmaterial
\vfill
\eject
%==============================================
\newdimen\savedvsize
\newbox\savedORbox
% now for a set of special output routines:
% the first one does the following
%
% - save away current \vsize and set it to \maxdimen
% - then unbox 255 and readd the output penalty (unless it is 10000)
% - set up a new output routine for the next time
% - finally install control with \aftergroup\addendpenalty
%
% The point here is that the \aftergroup token is not actually
% directly executed the moment the OR ends. If TeX ends an OR it looks
% at the recent contribution and if they are not empty it will call
% ``buildpage'' and move them to the main vertical list. And only if
% this has happened the \aftergroup token gets executed. Now given
% that we set \vsize to the largest possible dimen this means that all
% the remainder that was not used first time around will now end up on
% the main vertial list and only then \addendpenalty kicks in.
\output{%
\global\savedvsize\vsize
\global\vsize\maxdimen
%--- tracing --------------
\showthe\outputpenalty
\showlists
%--------------------------
\unvbox255
%
% above I claimed we put \outputpenalty back (which we should) but to
% make things more visible I put back a special penalty that can be
% easily recognised in \showlists
%
% \penalty \ifnum\outputenalty=10000 0 \else \outputpenalty \fi
\penalty-777
\global\output{\ORtwo}%
\aftergroup\addendpenalty
}
% The macro \addendpenalty is used with \aftergroup from the output
% routine to gain control again. It adds a penalty to trigger the next
% output routine. However, we are quite likely in horizontal mode when
% the OR returns (just have seen the start of a paragraph) so we check
% for this. If true we remove the indentation box and end the
% paragraph. As a result all that get contributed is \parskip but no
% box (so \prevdepth is not touched). We signal with the penalty value
% whether or not we have seen hmode as we will have to remove that
% extra parskip in the next OR.
\def\addendpenalty{%
\ifhmode
\setbox0\lastbox\par\penalty-10010
\else
\penalty-10011
\fi}
% the second Or now should receive everything that was on the main
% vertical list with the recent contributions being empty (well empty
% except for a \penalty10000 that TeX puts in the place where it
% triggered the OR).
%
% What we have to do now is to remove the surplus \parskip at the
% bottom of 255 if we have been in hmode before. This is something we
% can determine by looking at the \outputpenalty that should be -10010
% in that case (otherwise -10011)
%
% then we save all of 255 in a spare box and return from the OR. To
% gain control afterwards we issue \aftergroup\XXX
\def\ORtwo{%
%--- tracing --------------
\showthe\outputpenalty
\showbox255\showlists
%--------------------------
\ifnum\outputpenalty=-10010
\setbox255=\vbox
{\unvbox255
\unskip % this gets rid of the \parskip from hmode
}
\fi
\global\setbox\savedORbox\box255
\aftergroup\XXX
}
% Note that now the macro \XXX is immediately called when the OR ends
% as the recent contributions are empty now. Thus this macro now is
% getting us in a good shape:
%
% - it can access \prevdepth and \prevgraf (which is in fact having
% the same issue) and it can change them as necessary.
%
% - it has the complete main vertical list at its disposal (saved
% in \savedORbox)
%
% - there is nothing left in recent contributions so anything
% following is new material, so we can now arrange everything to our
% liking and reprocess
\def\XXX{%
%--- tracing --------------
\showthe\prevdepth % this is finally the outer one and we could
% change it if needed
\showlists % nothing on it not even the penalty remains
% only prevdepth and prevgraf set (incorrectly)
%--------------------------
% \global\vsize\savedvsize
\global\vsize20\baselineskip
\global\output{\plainoutput}%
\unvbox\savedORbox
}
% what we do above is set the vsize back to 20 baselines set up the
% plainoutput routine again and reprocess and we get 100% the same as
% in the initial test (well, in one place there is penalty 777 but
% that was just mark that spot, normally we would have \outputpenalty
% there which was 0. In the original there was nothing in this space
% only glue but that is equivalent
% and here is now the real test: we set a very short vsize so the
% first OR is triggered with \testmaterial and some part of it ends up
% in recent contributions.
\vsize=3\baselineskip
\testmaterial
\bye
答案2
当 OR 将某些内容返回到主垂直列表(RET 材料)时,将插入此 RET 材料(粗略地说,在这种情况下我们不考虑插入)而是用 box255 包装的材料。无论其后是否有非空余项。RET 材料和非空余项之间的行间粘连不需要通过和再次计算,\baselineskip
因为\prevdepth
跳过在这里已经计算过了。您只需要模拟 RET 材料的深度等于 box255 的深度。另一方面,如果余项为空,则全局变量将\prevdepth
保留 box255 的值,并且行间计算假定前一个框具有此深度。您只需要模拟 RET 材料的深度等于 box255 的深度。所以,没有区别。
因为 OR 知道:box255 的深度和 RET 材料的深度,所以它可以简单地进行校正。请注意,我只在您的特殊 OR 代码中修改了您的示例。
\tracingonline=1
\showboxbreadth\maxdimen\showboxdepth\maxdimen
test with g to get a depth
\showthe\prevdepth % we see the prevdepth from the last line
\output{\edef\depthcclv{\the\dp255}%<<< --- this code was added by wipet
\setbox0=\vbox{\unvbox255}%
next line will be too close\par
\kern-\prevdepth \kern\depthcclv %<<< --- this line was added by wipet
\showlists
\showthe\prevdepth
}
\eject
% now we see that the prevdepth should be (and is 0pt) last line jiust contains
% characters without depth
\showlists
\showthe\prevdepth
% but now we got the old \prevdepth back even though it is no longer valid, as
% the OR simply pops the nest even though it is no longer valid.
% As a result we will get the wrong alignment on the next paragraph
Second line \par
\showlists
% here we can see that we are off by the 1.9...pt prevdepth as we should see
% 12pt baseline to baseline but we don't
\output{\plainoutput}
\bigskip
But we really should see:
Next line will not be too close \par Second line
\bye