如何在 vsplit 中保留 parskip?

如何在 vsplit 中保留 parskip?

如果我用非零值框住某个内容\parskip,然后使用 将其拆分\vsplit,则生成的拆分框将具有所需的准确高度。这通常比我想要的要多,因此我会立即\unvbox拆分框并重新拆分\vbox以删除底部多余的空白。这种方法通常有效,除非您有非零值\parskip并且拆分点位于段落之间。考虑 MWE:

\parskip=7pt

\setbox0=\vbox{\input knuth\par}

\splittopskip=0pt
\setbox1=\vsplit 0 to 2\baselineskip
\setbox1=\vbox{\unvbox1}
\ifdim\dp1<\dp\strutbox
  \dp1=\dp\strutbox
\fi

\box1
\box0

\bye

正如您在输出中看到的,\parskip前两段之间缺少 ,因为那是进行拆分的地方。

在此处输入图片描述

有没有办法检测分割框的最后一项是否是\parskip?也欢迎使用 LuaTeX 解决方案。


编辑:我意识到如果中断发生在该点,则\vsplit立即丢弃\parskip。这个简单的例子很容易说明这一点:

\parskip=7pt

\setbox0=\vbox{\input knuth\par}

\setbox1=\vsplit 0 to 2\baselineskip
\vbox{\unvbox1 \unvbox0}

\bye

在此处输入图片描述

答案1

如果你确实希望框具有精确的高度,LuaTeX 支持

\setbox1=\vsplit 0 upto 2\baselineskip

作为内置替代方案

\setbox1=\vsplit 0 to 2\baselineskip
\setbox1=\vbox{\unvbox1}

正如您已经看到的,这并没有真正解决问题,因为 parskip 已被丢弃\vsplit。但 LuaTeX 还允许保存和检查从 Lua 拆分中丢弃的元素,因此我们可以重新插入 parskip:

\directlua{
  function my_split()
    % Scan the parameters
    local result_box = token.scan_int()
    token.scan_keyword'='
    local original_box = token.scan_int()
    token.scan_keyword'upto'
    local height = token.scan_dimen()

    % Temporarly set tex.savingvdiscards
    local savediscards = tex.savingvdiscards
    tex.savingvdiscards = 1

    % Do the actual split
    local b = tex.splitbox(original_box, height, 'additional')
    tex.savingvdiscards = savediscards

    % Analyze tex.lists.split_discards_head to find the parskip
    local current = tex.lists.split_discards_head
    if current then current.prev = nil end
    while current do
      if current.id == 12 and current.subtype == 3 then
        local this = current
        tex.lists.split_discards_head, current = node.remove(tex.lists.split_discards_head, current)
        b.head = node.insert_after(b.head, nil, this)
        b.height = b.height + this.width
      else
        current = current.next
      end
    end
    % Save the result 
    tex.box[result_box] = b
  end
}
\protected\def\setparsplit{\relax\directlua{my_split()}}

\setparsplit可以用作

\directlua{
  function my_split()
    % Scan the parameters
    local result_box = token.scan_int()
    token.scan_keyword'='
    local original_box = token.scan_int()
    token.scan_keyword'upto'
    local height = token.scan_dimen()

    % Temporarly set tex.savingvdiscards
    local savediscards = tex.savingvdiscards
    tex.savingvdiscards = 1

    % Do the actual split
    local b = tex.splitbox(original_box, height, 'additional')
    tex.savingvdiscards = savediscards

    % Analyze tex.lists.split_discards_head to find the parskip
    local current = tex.lists.split_discards_head
    if current then current.prev = nil end
    while current do
      if current.id == 12 and current.subtype == 3 then
        local this = current
        tex.lists.split_discards_head, current = node.remove(tex.lists.split_discards_head, current)
        b.head = node.insert_after(b.head, nil, this)
        b.height = b.height + this.width
      else
        current = current.next
      end
    end
    % Save the result 
    tex.box[result_box] = b
  end
}
\protected\def\setparsplit{\relax\directlua{my_split()}}
\parskip=7pt

\setbox0=\vbox{\input knuth\par}

\splittopskip=0pt
\setparsplit1=0 upto 2\baselineskip

\ifdim\dp1<\dp\strutbox
  \dp1=\dp\strutbox
\fi

\box1
\box0

\bye

答案2

首先,您的示例包含不精确的尺寸测量。您设置\splittopskip为 0pt,因此 box1 的结果包括两条线,其高度为第一条线加上一个基线跳跃。但是您说\vsplit to2\baselineskip,所以其余两个基线跳跃会在未满的 box1 内的这两条线下方创建一个空白空间。如果您使用,\setbox1=\vbox{\unvbox1}那么这个空白空间将被删除。

此外,下一个 box0 以其可变高度的线开始,因此即使你修正了 box1 的深度,也无法在我们的示例中保持第二行和第三行之间的统一基线距离。因此,这些问题的解决方案是

  • 保持\splittopskip价值 10pt
  • 使用技巧\penalty0作为第一个元素和\vsplit0 to0pt第一个拆分操作:它在第一行添加 topskip,因此第一个 baselineskip 正好距离顶部 10pt。
  • 使用更精确计算的分割点作为 1 topskip 加 (n-1) baselineskips。

该代码不能解决您所提出的问题,但可以解决我提到的问题:

\parskip=7pt

\setbox0=\vbox{\penalty0 \input knuth\par}

\splittopskip=10pt
\setbox1=\vsplit0 to0pt

\dimen0=\splittopskip \advance\dimen0 by 1\baselineskip
\setbox1=\vsplit 0 to \dimen0
\setbox1=\vbox{\unvbox1}
\ifdim\dp1<\dp\strutbox
  \dp1=\dp\strutbox
\fi

\box1
\box0

\bye

现在,我们可以解决您的问题了。您可以\par在本地重新定义何时将数据读入 box0,并检查最后的惩罚是否为 10001。如果这是真的,那么您可以添加\vskip\parskip

\parskip=7pt

\def\redefpar{\def\par{\endgraf \penalty10001 \penalty0 }}

\setbox0=\vbox{\penalty0 \redefpar \input knuth\par}

\splittopskip=10pt
\setbox1=\vsplit0 to0pt

\dimen0=\splittopskip \advance\dimen0 by 1\baselineskip
\setbox1=\vsplit 0 to \dimen0
\setbox1=\vbox{\unvbox1
  \ifnum\lastpenalty=10001 \penalty0 \vskip\parskip\fi}
\ifdim\dp1<\dp\strutbox
  \dp1=\dp\strutbox
\fi

\box1
\box0

\bye

答案3

这很大程度上是基于 @wipet 的回答。我对此进行了一些实验,以下是我的版本,可以更好地重建垂直间距。

pdftex默认情况下具有 e-TeX 扩展,我在这里(不必要地)使用过一次)

\parskip=7pt

\def\redefpar{\def\par{\endgraf \penalty10001 \penalty0 }}

\setbox0=\vbox{\penalty0 \redefpar \input knuth\par}

\splittopskip=10pt
\setbox1=\vsplit0 to0pt

\dimen0=\splittopskip \advance\dimen0 by 1\baselineskip
\setbox1=\vsplit 0 to \dimen0
\dimen0\dp1
\setbox1=\vbox{\unvbox1
  \ifnum\lastpenalty=10001 \penalty0 \vskip\parskip\fi
  }

\box1
\nointerlineskip
\kern-\dimen0
\vskip\dimexpr\baselineskip-\splittopskip\relax
\box0

\tracingoutput 1 %\showboxbreadth \maxdimen \showboxdepth \maxdimen

\bye

顺便说一下,我正在使用这个knuth.tex文件

Thus, I came to the conclusion that the designer of a new system must not only
be the implementer and first large--scale user; the designer should also write
the first user manual.

The separation of any of these four components would have hurt \TeX\
significantly. If I had not participated fully in all these activities,
literally hundreds of improvements would never have been made, because I would
never have thought of them or perceived why they were important.

But a system cannot be successful if it is too strongly influenced by a single
person. Once the initial design is complete and fairly robust, the real test
begins as people with many different viewpoints undertake their own
experiments.

我从 ConTeXT 的 PDF 文档中复制了源代码,它以小字体显示。我不知道原始来源在哪里可以找到。

相关内容