有没有办法从输出例程中的主垂直列表中获取 \prevdepth 的值?

有没有办法从输出例程中的主垂直列表中获取 \prevdepth 的值?

为了激发这个(无可否认)奇怪的问题:

当 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

相关内容