\intextsep 提供双倍空间

\intextsep 提供双倍空间

如果浮动(黑框)跟随我的{example}环境(灰色左线),它们之间的间距太大。我猜是两倍,\baselineskip但应该只有一个\baselineskip加上一些粘连。它应该与浮动和后续文本之间的间距相同。

error

我读到source2e它的行为与我示例中的双倍空间\intextsep不同\addvspace。可以修复此行为吗?还是我必须\vspace{-\baselineskip}手动插入所有地方?

{example}我也尝试在via周围插入空格\addvspace而不是mdframed方法(将 skip 设置为选项)但这也不起作用。

代码

\documentclass[english]{scrartcl}

\usepackage{mdframed}
\newenvironment{example}[1][]{%
    \begin{mdframed}[%
        skipabove={\baselineskip plus 5pt minus 1.5pt},
        skipbelow={\baselineskip plus 5pt minus 1.5pt},
        middlelinewidth=3pt,
        middlelinecolor={black!60},
        splitbottomskip=1pt,
        splittopskip=8pt,
        innertopmargin=2pt,
        innerbottommargin=2pt,
        innerleftmargin=6.89749pt,
        innerrightmargin=0pt,
        topline=false,
        bottomline=false,
        rightline=false,
    ]%
}{%
    \end{mdframed}
}

\usepackage{babel,blindtext}
\begin{document}
\Blindtext
\begin{example}
\blindtext
\end{example}
\begin{table}[h]
\rule{\textwidth}{5cm}
\end{table}
\Blindtext
\end{document}

更新

我第一次尝试设置\intextsep=0pt并添加\addvspace{\intextsep}用户级浮动环境后,发现它并不适用于所有情况(如果浮动浮动,也会插入空格),我再次查看source2e并找到了\@addtocurcol宏,它似乎是插入浮动及其周围空格的宏。其中有两行\intextsep通过插入\vskip

\vskip \intextsep
\box\@currbox
\penalty\interlinepenalty
\vskip\intextsep

因此,我尝试改变它们的宽度\pathcmdetoolbox并且它适用于浮点数遵循示例的情况,但不适用于浮点数在示例之前的情况。

\patchcmd{\@addtocurcol}%
    {\vskip \intextsep}%
    {\addvspace{\intextsep}}{}{}
\patchcmd{\@addtocurcol}%
    {\vskip\intextsep}{}{}{}

我也尝试将第二个放在\addvspace其他地方,但没有成功。

我想这\addvspace两个{table}不能{example}如果浮点数位于示例之前,则相互确定正确的间距。

M(半)WE

\documentclass[english]{scrartcl}

\usepackage[framemethod=TikZ]{mdframed}
\newenvironment{example}[1][]{%
    \par\addvspace{\baselineskip}%
    \begin{mdframed}[%
%       skipabove={\baselineskip plus 5pt minus 1.5pt},
%       skipbelow={\baselineskip plus 5pt minus 1.5pt},
        middlelinewidth=3pt,
        middlelinecolor={black!60},
        splitbottomskip=1pt,
        splittopskip=8pt,
        innertopmargin=2pt,
        innerbottommargin=2pt,
        innerleftmargin=6.89749pt,
        innerrightmargin=0pt,
        topline=false,
        bottomline=false,
        rightline=false,
    ]%
}{%
    \end{mdframed}\par\addvspace{\baselineskip}%
}

\setlength{\intextsep}{\baselineskip}
\usepackage{etoolbox}
\makeatletter
\patchcmd{\@addtocurcol}%
    {\vskip \intextsep}%
    {\addvspace{\intextsep}}%
    {\typeout{*** SUCCESS ***}}{\typeout{*** FAIL ***}}
\patchcmd{\@addtocurcol}%
    {\vskip\intextsep}{}%
    {\typeout{*** SUCCESS ***}}%
    {\typeout{*** FAIL ***}}
\patchcmd{\@addtocurcol}%
    {\@inserttrue}%
    {\@inserttrue\addvspace{\intextsep}}%
    {\typeout{*** SUCCESS ***}}{\typeout{*** FAIL ***}}
\makeatother

\usepackage{babel,blindtext}
\begin{document}
\Blindtext\blindtext
\begin{table}[h]
    \rule{\textwidth}{5cm}
\end{table}
\begin{example}
    \blindtext
\end{example}
\begin{table}[h]
    \rule{\textwidth}{5cm}
\end{table}
\Blindtext
\begin{example}
    \blindtext
\end{example}
\blindtext
\end{document}

结果

result

更新 2

这是包含 Frank 解决方案的代码,但它仍然无法正常工作。 和第一个表之间(即示例上方)的空间太大{example}。 如果我注释掉示例,它似乎也无法工作:两个浮点数之间的空间比浮点数和文本之间的空间更大。

\documentclass[english]{scrartcl}

\usepackage[framemethod=TikZ]{mdframed}
\newenvironment{example}[1][]{%
    \par\addvspace{\baselineskip}%
    \begin{mdframed}[%
%       skipabove={\baselineskip plus 5pt minus 1.5pt},
%       skipbelow={\baselineskip plus 5pt minus 1.5pt},
        middlelinewidth=3pt,
        middlelinecolor={black!60},
        splitbottomskip=1pt,
        splittopskip=8pt,
        innertopmargin=2pt,
        innerbottommargin=2pt,
        innerleftmargin=6.89749pt,
        innerrightmargin=0pt,
        topline=false,
        bottomline=false,
        rightline=false,
    ]%
}{%
    \end{mdframed}\par\addvspace{\baselineskip}%
}

\setlength{\intextsep}{\baselineskip}
\usepackage{etoolbox}
\makeatletter
\patchcmd{\@addtocurcol}%
    {\vskip \intextsep}%
    {\addvspace{\intextsep}}%
    {\typeout{*** SUCCESS ***}}{\typeout{*** FAIL ***}}

\patchcmd{\@addtocurcol}%
    {\vskip\intextsep}{\fix@second@intextsep}%
    {\typeout{*** SUCCESS ***}}%
    {\typeout{*** FAIL ***}}

\def\fix@second@intextsep {%
% was the float seen in vertical mode?
   \ifnum\outputpenalty <-\@Mii 
      \aftergroup\vskip\aftergroup\intextsep
  \else
      \vskip\intextsep
  \fi
}
\makeatother

\usepackage{babel,blindtext}
\begin{document}
\Blindtext\blindtext
\begin{table}[h]
    \rule{\textwidth}{5cm}
\end{table}
\begin{example}
    \blindtext
\end{example}
\begin{table}[h]
    \rule{\textwidth}{5cm}
\end{table}
\Blindtext
\begin{example}
    \blindtext
\end{example}
\blindtext
\end{document}

答案1

这有点棘手。看来你又发现了 2e 中该部分的另一个缺陷(如果我们不想称之为错误的话)。

首先我们必须了解它\addvspace的作用:它查看前面是否有正(!)垂直粘连,如果是,它会做一些神奇的事情来添加这个粘连的最大值和它应该添加的粘连。如果它必须修改现有的粘连,它会先添加一个负粘连来取消现有的粘连,然后添加它的一个粘连。这种迂回的方式是必要的,因为\unskip主垂直列表上不能有粘连,所以真正的删除是不可能的。它对后面的任何东西都不做任何特别的事情——这必须由下一个addvspace或来处理\addpenalty

考虑到这一点,第一个改变\@addcurcol是正确的:用 替换,\vskip \intextsep然后\addvspace\intextsep可以与浮子之前的任何胶水很好地结合。

但是,尝试更改框后的第二个跳过是毫无意义的。为什么?因为在它之前只有一个框,没有任何胶水,所以\addvspace这里相当于除了\vskip它进行一些不必要的查找和测试以找出其中的内容。

那么为什么\addvspace下一个环境中的后续操作看不到这个跳过呢?答案在于 TeX 实现输出例程调用的方式。出于效率原因(我的猜测),Don 决定将触发输出例程的惩罚项保留为“最近贡献”中的第一个项,并将其值更改为 10000。换句话说,该惩罚永远不会再产生中断。这是算法中一个有点令人讨厌的部分,也是在各种情况下需要大量额外编程的原因。在这里,这意味着我们最终会得到以下序列:

\glue \intextsep
\box (float)
\glue \intextsep
\penalty 10000   (from the initial \penalty -1000x triggered by the float)

因此,任何后续的 `\addvspace 都只会看到惩罚而不会看到粘合。

那么该怎么办呢?答案是采用\@addcurcol不同的修补方法:

\makeatletter
\patchcmd{\@addtocurcol}%
    {\vskip \intextsep}%
    {\addvspace{\intextsep}}%
    {\typeout{*** SUCCESS ***}}{\typeout{*** FAIL ***}}
\patchcmd{\@addtocurcol}%
    {\vskip\intextsep}{\aftergroup\vskip\aftergroup\intextsep}%
    {\typeout{*** SUCCESS ***}}%
    {\typeout{*** FAIL ***}}
\makeatother

这将导致第二个\vskip仅在输出例程结束后才执行,因此将移动粘连到最后,以便可以被后续例程看到\addvspace

更新一 + 二

不幸的是,上述解决方案过于简单。如果“此处”浮动发生在垂直模式下,则它可以工作。但是,如果“此处”浮动位于段落中间,则“最近贡献”将不仅包含惩罚,当然还包含段落中位于浮动行之后的任何行。因此,我们的\aftergroups 不会将空格放在浮动之后,而是将其弹射到整个段落之后。因此,我们需要使代码依赖于遇到浮动的模式。幸运的是,LaTeX 在触发输出例程时通过使用不同的惩罚来发出信号。

还有一个复杂的问题:在 2e 方法中

  \ifnum\outputpenalty <-\@Mii \vskip -\parskip\fi

如果在垂直模式下遇到浮动,则将备份\parskip。这样做的原因是浮动后的下一行文本将\parskip在这种情况下添加一个。但是,正如 Tobi 在他的评论中提到的那样,如果两个“here”浮动直接紧挨着彼此,那么它们之间的空间似乎是错误的。事实上,在这种情况下没有\parskip添加,所以取消它意味着我们实际上正在缩短它所容纳的空间。现在没有办法知道浮动之后是什么,所以没有任何认真的簿记,我能看到的唯一简单的解决方案是删除上面的行(即,\parskip除了浮动之后还允许\intextsep),而是在浮动之前添加它)。然后垂直模式下“here”浮动周围的间距将始终\parskip+\intextskip在两侧,并且两个这样的浮动之间也会发生同样的情况。

最后,在浮动上方添加的空间将仅插入到最后一行文本的底部之间(如果该行包含带有降部(如“g”)的字符,则不从其基线测量)。但是,在浮动下方,不仅有添加的空间,还有一些计算出来的额外空间\baselineskip(有趣的是,这还取决于\prevdepth浮动之前行的,因此它是双重错误的)。因此,我建议\baselinskip此时使用 完全抑制更正\nointerlineskip

因此,希望(最终)正确的解决方案如下所示:

\makeatletter
\patchcmd{\@addtocurcol}%
    {\ifnum\outputpenalty <-\@Mii \vskip -\parskip\fi}%
    {}%
    {\typeout{*** SUCCESS ***}}{\typeout{*** FAIL ***}}

\patchcmd{\@addtocurcol}%
    {\vskip \intextsep}%
    {\addvspace\intextsep
      \ifnum\outputpenalty <-\@Mii \vskip\parskip\fi}%
    {\typeout{*** SUCCESS ***}}{\typeout{*** FAIL ***}}

\patchcmd{\@addtocurcol}%
    {\vskip\intextsep}{\fix@second@intextsep}%
    {\typeout{*** SUCCESS ***}}%
    {\typeout{*** FAIL ***}}

\def\fix@second@intextsep {%
% was the float seen in vertical mode?
   \ifnum\outputpenalty <-\@Mii 
      \aftergroup\vskip\aftergroup\intextsep
      \aftergroup\nointerlineskip
  \else
      \vskip\intextsep
  \fi
}
\makeatother

现在,这个扩展解决方案\parskip在垂直模式下添加了“here”浮动,因此如果此参数具有正值,那么与段落中间的“here”浮动相比,现在存在明显差异。也许这使得这种方法在这种情况下不那么有吸引力。但是,检测两个“here”浮动是否真的直接相继出现将非常困难(并且肯定无法通过几\patchcmd行来管理),所以我想这是最好的办法。

更新三

Tobi 测试了上述解决方案,并发现了另一种无法解决的情况:如果在一个段落内(即,在水平模式内)有两个连续的“h”浮点数,那么\intextsep它们之间会插入 2 个空格而不是 1 个空格。

这种行为的原因是,在 hmode 中,我在\vskip\intextsep输出例程内添加了 ,因此在触发 OR 的惩罚之前而不是之后使用\aftergroup。这样做有一个很好的理由(参见上面的讨论),但当然,如果两个这样的浮点数在段落内直接接连出现,那么这意味着\intextsep第二个浮点数添加的 无法与前一个浮点数的 结合,因为惩罚导致它看不到它。所以看起来我们陷入了困境:要么跳过保持隐藏,要么它可能在整个段落之后被弹射,因此应该出现在错误的位置。

幸运的是,我们还可以应用另一个技巧:如果我们不在主垂直列表中(而是构建内部垂直列表,输出例程中就是这种情况),我们可以使用原语来\unpenalty查看当前列表中的最后一个元素,如果它是惩罚项,则将其删除。这样就有可能摆脱阻碍\penalty 10000我们前进的东西,然后\addvspace可以跳过这个惩罚项。

现在我们需要考虑的一点是,通常有 2 个惩罚需要消除:一个是来自输出例程的惩罚,即 10000,另一个是之前插入的惩罚\@addtocurcol\addpenalty\interlinepenalty现在的问题是:如果我们立即尝试再次删除它,我们是否需要这个惩罚?答案是“可能”是的,至少不需要做太多手术。重点是,由于使用了\addpenalty,它后面可能会有一个跳过(从上面迁移过来)。所以我们确实需要尝试\unpenalty两次,并希望以这种方式捕捉\penalty 10000输出例程的剩余部分。如果以这种方式发现的第二个惩罚不是,10000我们将第一个惩罚放回去(并希望得到最好的结果)。这可能发生在许多情况下,所以我们需要考虑它。

我们可以尝试的另一个改进是支持段落内浮动和段落间浮动的两个不同的跳过值(所有段落间的浮动都会被\parskip添加,请参阅之前的更新)。因此,这为我们提供了以下一组新补丁:

\newlength\intextvsep  % like \intextsep but only for floats in vmode
\setlength\intextvsep{\intextsep}

\patchcmd{\@addtocurcol}%
    {\vskip \intextsep}%
    {\edef\save@first@penalty{\the\lastpenalty}\unpenalty
     \ifnum \lastpenalty = \@M  % hopefully the OR penalty
        \unpenalty
     \else
        \penalty \save@first@penalty \relax % put it back
     \fi
      \ifnum\outputpenalty <-\@Mii
                         \addvspace\intextvsep
                         \vskip\parskip
      \else  
                         \addvspace\intextsep   
      \fi}%
    {\typeout{*** SUCCESS ***}}{\typeout{*** FAIL ***}}

\patchcmd{\@addtocurcol}%
    {\vskip\intextsep \ifnum\outputpenalty <-\@Mii \vskip -\parskip\fi}%
    {\fix@second@intextsep}%
    {\typeout{*** SUCCESS ***}}{\typeout{*** FAIL ***}}

如果我们对跳过使用两个单独的参数,那么我们还需要对该命令进行稍微更改:

\def\fix@second@intextsep {%
% was the float seen in vertical mode?
   \ifnum\outputpenalty <-\@Mii 
      \aftergroup\vskip\aftergroup\intextvsep
      \aftergroup\nointerlineskip
  \else
      \vskip\intextsep
  \fi
}

如果我们使用它,它看起来已经相当不错了。但是它仍然存在问题。如果用户在段落内有一个“h”浮动,并试图阻止在同一行分页,那么就会出错,例如:

Bla bla \nopagebreak[4] bla bla
\begin{table}[!h] -A- \end{table}
bla bla

这将产生一系列

<text line>
\penalty 10000   % from \nopagebreak not from a previous OR call !!!!!
\penalty 0       % from the interlinepenalty added by \@addtocurcol

因此我们的两次\unpenalty呼叫将会看到两次处罚,但会做出错误的扣分并取消不间断。糟糕...

甚至可以修复这个问题,但这意味着进一步的改变:由 增加的惩罚\nopagebreak是由某个命令生成的,\getpen该命令查看可选参数并根据可选参数插入不同的惩罚值。一个简单的想法是将惩罚从\@M(10000) 更改为,\@Mi因为这样我们上面的测试就会失败,我们就可以了。但不幸的是,\@getpen也用于\pagebreak,这意味着我们将在那里插入一个惩罚-10001,这表明是浮动而不是分页符,结果我们得到了 LaTeX 错误“浮动丢失”。因此,我们必须区分这些情况,并在防止和强制分页时添加不同的惩罚:

\def\pagebreak{\@testopt{\@@pgbk}4}
\def\@@pgbk [#1]{%
  \ifvmode
    \penalty \@getpen@{#1}%
  \else
    \@bsphack
    \vadjust{\penalty \@getpen@{#1}}%
    \@esphack
  \fi}
\def\@getpen@#1{-\ifcase #1 \z@ \or \@lowpenalty\or
         \@medpenalty \or \@highpenalty
         \else \@M \fi}

% and here the definition used by \nopagebreak internally with a tiny change:
\def\@getpen#1{\ifcase #1 \z@ \or \@lowpenalty\or
         \@medpenalty \or \@highpenalty
         \else \@Mi \fi}

\@getpen也用于\linebreak和朋友,但是这里 10000 或 10001 没有区别,所以我们应该没问题。

这无法解决将“h”浮点数与看起来有点像“h”浮点数(例如列表或 mdframed)但根本不使用浮动机制的对象(即不触发输出例程来放置对象)组合的问题。如果这样的对象在段落内彼此相邻放置,则所有使用等的操作\unpenalty都是不可能的(因为这在主垂直列表中是不允许的),因此我们将遇到一种情况,即\addvspace无法\intextsep从“h”浮点数看到前一个,因此如果不进行手动校正,间距就会出错。解决这个问题的唯一方法(我能想到的)是将这些对象也变成浮点类,然后使用这些类的“h”浮点数的实例。

还有什么可以改进的?正如“parskip 在浮动(和列表)后插入额外空格“段落内浮动的“h”的垂直定位并不完美,\strut在这种情况下,一种可能的改进方法是使用。我们可以自动执行此操作,因为我们正在修补此区域,因此这里还有此代码:

\patchcmd{\end@float}%
    {\vadjust}{\strut\vadjust}%
    {\typeout{*** SUCCESS ***}}{\typeout{*** FAIL ***}}

概括

我认为整个区域都显示出 2e 代码的明显缺陷(如果不是说是错误的话),但我现在可以说 2e 的内核不会有任何变化。这会破坏/更改太多根据当前间距规则格式化的文档。

我把更新一个接一个地留下,因为我认为这样可以使一些围绕 OR 算法的问题更容易理解。

float正如 Tobi 在下面提到的那样,如果该包也被使用,则需要以某种形式为来自该包的命令添加上述补丁。

答案2

当我将 Frank 的出色回答纳入我的项目时,我注意到它不适用于[H]浮点数(来自float.sty)。因此必须修补\float@endH

\patchcmd{\float@endH}{\vskip\intextsep}%
      {\addvspace\intextsep\ifnum\outputpenalty <-\@Mii \vskip\parskip\fi}{}{}
\patchcmd{\float@endH}{\@currbox\vskip\intextsep}%
      {\@currbox\vskip7.5pt\def\fix@second@intextsep}{}{}

我想7.5pt跳过必须单独进行调整,但这个对我的文本有用。

相关内容