获取每条线的 \badness 或 glue 调整

获取每条线的 \badness 或 glue 调整

对于想要进一步改进文档的完美主义者和/或一丝不苟的排字员来说,这可能会很有趣(超越没有坏框的书的壮丽)。

我们都知道,连字算法是由 Franklin Mark Liang 先生构想的,并在帕特根程序,基于对一大堆带连字符的单词的处理,计算允许断开的可能性,构建一个紧凑的表格以节省空间和内存等。它可以正确识别多达 90% 的可能断开,具体取决于语言。然而,鉴于当今的计算机不再受 '82 的限制,*TeX 输出中的连字符可以通过两种方式进一步改进:

  1. 我们可以为每种语言创建一个全面的连字数据库,并获得 100% 的准确率。
  2. 如果问题出现,我们可以处理:分析记录的报告,查找任何超额情况,如果它们的词根可以追溯到未发现的允许连字点,则手动将该单词添加到的“白名单”中\hyphenation{...}

虽然在英语中这似乎不是问题,因为短词太多,但在俄语和德语中却经常出现这种情况:有时由于错过连字符点,我得到 5 个溢出的框,经过一些类似于的硬编码后\hyphenation{ми-н-да-лём ра-с-по-ря-ди-те-лю мо-ж-но},所有坏框都消失了。作为俄语文本的排版员,我可以向你保证,在经过 TeX with 完美[russian]{babel}处理所有各种标点符号之后,它们看起来很完美,但是将 () 单字母单词(理想情况下是一些双字母助词)绑定到下一个单词的做法~会因一些未找到的连字符点而变得黯然失色,从而导致溢出。由于很多书籍都很紧凑,文本区域通常限制在 100×175 毫米,甚至更小。相信我,这对西里尔文本的排版员来说是一个真正的挑战。

问题概括为一句话:一个遗漏的断字点会导致一些低劣的断行或接近美学上不可接受的断行,而手动连字后,使用新的断行点,可以改善断行问题;然而,手动“堵漏”并不好玩。

任何额外的不可分割空格(~)都是一种限制,我们都知道,从数学上讲,它无法降低段落的总体“糟糕程度”。任何额外的条件都是一种妥协,它会导致通过三次函数最小化获得的条件最小值与没有施加限制时相比有所增长(回归分析也是如此:受限制的残差平方和大于不受限制的残差平方和)。问题因以下事实而加剧:如果一行的糟糕程度不超过 1000,但非常接近,TeX 不会报告

如果我能追踪并找到糟糕程度为 990 的一行,而这行之所以糟糕,只是因为连字算法没有找到单词的断点,那就更好了!如果能看到所有遗漏断点的位置,并增加更多自由度,从而改善外观,那就更好了(如果一个单词无论如何都要断掉,最好给它一个语言规则允许的最大断点数!)。

这让我想到了两种解决这个问题的可能方法:

  1. 制作并编译一个 DIY 修改版的 pdfLaTeX,它会报告所有\badness超出的情况X(比如说 700)在每一行中,单词都必须用连字符连接,这无疑是一种肮脏的黑客行为;
  2. 编写一个扩展,在每一行之后显示不良情况(一种“über- draft”模式,它不仅在发生溢出的地方打印一个黑色矩形,而且还报告单词间空间接近其最大或最小允许值的实例。

我以为可能在 LuaTeX 中,标准单词间距中添加的胶水的绝对量(如果我没记错的话,3.33333pt 加上 1.66666pt 减去 1.11111pt)打印在页边距中是可能的。如果在 LuaTeX 中可以做到这一点,那么它可以进一步变得更加用户友好:打印缩小或扩大的可能量的百分比……并着色(毕竟这是 LuaTeX!)。更新:但显然它使用了不同的字体和度量标准,而这样的解决方案对任何 LaTeX 排版人员都没有帮助,据我粗略估计,排版人员占 TeX 用户的绝大多数,而且这个比例不太可能动摇。

如果microtype启用了包,则会出现同样的问题:我们能否获取stretch/shrink每行输出的参数值?如果默认限制为 20,则在发生单词中断的行中,值为 20 或 -19 可能表示找不到连字点,因此不得不采取极端扩展/压缩。

虽然从长远来看,过渡到全尺寸连字词典可能是最漂亮的选择(假设连字搜索算法的复杂度不超过……比如说,O(n·日志(n)), 在哪里n是字典大小的度量),我现在想要的是确定打印/存储每条线的不良率和/或添加/去除的胶水的确切数量的可能性。

一句话概括所需结果:检查\badness接近临界值的 es 出现情况或接近允许最大值的胶水添加量(概念如图所示)。

报告添加的胶水量 报告不良率高于阈值

(这是一个可能成为 LaTeX 输出新质量标准的近似模型。)

您有何建议?

更新

我重现了一个令人困扰的例子,其中一个\hyphenation单词的手册大大改善了段落布局。

最小工作示例:

\documentclass[10pt]{memoir}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel} % Enable Russian hyphenation
\usepackage{microtype} % See how even microtype fails
\righthyphenmin=2 % Russian language rules
\def\psk{\hskip1.5em\relax} % Parboxes and all that hard-coded stuff just pursue
% the illustrative aim to reproduce the example precisely

\begin{document}
\parbox[t]{226.15pt}{\psk И~он показывал какую-то странную позу, несколько
запрокинувшись назад, как бы полупадая от «истомлённости».}
% The badness is very close to 1000, and you see how bad it is

\parbox[t]{226.2pt}{\psk И~он показывал какую-то странную позу, несколько
запрокинувшись назад, как бы полупадая от «истомлённости».}
% Now the badness is over 1000

\parbox[t]{226.15pt}{\psk И~он показывал какую-то странную позу, не\-с\-ко\-ль\-ко
запрокинувшись назад, как бы полупадая от «истомлённости».}
% Since there must a hyphen anyway, this breaking is much more beautiful now!
% (And such hyphenation is perfectly legitimate.)
\end{document}

由于缺少连字符点,错误率接近 1000

Underfull \hbox (badness 1009) in paragraph at lines 15--15
[] \T2A/cmr/m/n/10 (+20) И он по-ка-зы-вал какую-то стран-ную по-зу,

这就是我所说的:段落中必须有一个连字符,布局 1 和 3 都没有被报告为不好,但手动调整的 3 更漂亮。当然,可以多次运行文档\textwidth,例如,使用步骤 5 将文档大小从 220 pt 调整到 250 pt,并通过提供所有可能的断点来手动修改所有这些丑陋的线条,但是……你知道……LaTeX 文档不是通过一些硬编码来改进的,对吧?

注意:如果\parbox使用 es,则未找到的连字符会导致underfulls。如果将相同的宽度作为参数传递给geometry包,并且文本排版为普通段落,则未找到的连字符会导致overfulls。不过,两者都很可恶。

附言我知道http://tug.org/TUGboat/tb31-3/tb99isambert.pdfPaul Isambert 先生的文章介绍了一种 Lua(La)TeX 方法来查看页面灰度均匀度。此外,该chickenize包还提供了\colorstretch盲评估功能一切。但是,我并不看好 Lua(La)TeX 的稳健性和稳定性(就输入而言),因为有太多东西需要手动检测,并用一些手工拼凑的排版工具重新编码(细空格、细 nbsp、首字母空格——天哪,没有办法处理旧的babel)在 Unicode 中代替漂亮而体面的 LaTeX 宏!为了说明存在一个不太稳健的解决方案,如果 LaTeX 可能会重新实现,也可能不会,请参见以下示例(polyglossia的连字符也会崩溃):

\documentclass[10pt,oneside]{memoir}
\usepackage{fontspec}
\usepackage{polyglossia}
\usepackage{microtype} % See how even microtype fails
\righthyphenmin=2 % Russian language rules
\setmainfont{Liberation Serif}
\setdefaultlanguage{russian}
\setlength{\parindent}{1.5em}
\usepackage[textwidth=200.2pt]{geometry}
\usepackage{chickenize}

\begin{document}
\colorstretch
И~он показывал какую-то странную позу, несколько
запрокинувшись назад, как бы полупадая от «истомлённости».

И~он показывал какую-то странную позу, не\-с\-ко\-ль\-ко
запрокинувшись назад, как бы полупадая от «истомлённости».
\end{document}

LuaLaTeX 的 chickenize 输出

(在 Linux Mint Debian 上编译,没有安装任何附加字体。)好吧,这并不能重现手工制作的段落的精确间距和优点,但却稍微让我了解了我希望在 LaTeX 中看到的东西——一种检测可能由于错过连字符点而导致的劣质断字的方法。

答案1

一方面,它TeX通常比包括我在内的大多数人都更了解情况。另一方面,我一直对它不愿真正展示线路满载是怎么回事,主要是因为我发现消息中的数字几乎Overfull \hbox (7.79364pt too wide)没有什么信息量。

ConTeXt这就是我惊讶于发现诊断和引擎集成的原因之一LuaTeX。但是!这是一种传统的TeX解决方案。这就是我想到的:

卡波

让我来分解一下。

  • 左边的数字是线路不良率。
  • 左侧的框是线条灰度。它是通过将不良范围线性映射[0;100]到拉伸和收缩线条的灰度范围[#808080;#FFFFFF]和而获得的[#808080;#000000]。如果线条未满,则框为蓝色;如果线条过满,则框为红色。
  • 右侧的条形表示线条变形。橙色和天蓝色之间的边界位于线条的自然长度处。橙色(天蓝色)框是可用的收缩(拉伸)范围。实际线条宽度处有一条白色细线。如果线条不够饱满(过满),则会出现一条蓝色(红色)条形表示超出允许线条变形边界的缺失(过剩)长度。
  • 右边的数字为负(正)时收缩(拉伸)量,当管线未满(过满)时,该数字为缺失(过剩)的长度,即蓝(红)条的长度。

单行框评估例程的核心内容如下plain TeX; 我向你介绍badger.tex

% default rules abolished
\overfullrule=0pt

% ========================================================== GLUE STRAINING ====
% This is a procedure to detect glue finiteness and (if finite)
% quantify the total amount inside a box. Based on
% https://tex.stackexchange.com/a/191844/82186 by Bruno Le Floch

% to avoid choking on warnings
\hbadness=1000000
\hfuzz=\maxdimen

\newdimen\StrainedGlue
\newbox\tmp

\def\StrainGlue#1#2#3#4{
  \begingroup
    \dimen0 = -\maxdimen
    \dimen1 =  \maxdimen
    \loop
      \dimen2 = \dimen0
      \advance \dimen2 by \dimen1
      \divide \dimen2 by 2
      \ifdim \dimen2 = \dimen1
        \advance \dimen2 by -1sp \fi
      \setbox\tmp = \hbox spread #2 1sp {%
        \unhcopy#4\hskip 0pt #3 -\dimen2}
      \ifnum \badness > 100
        \dimen1 = \dimen2
      \else
        \dimen0 = \dimen2
        \advance \dimen0 by 1sp \fi
    \ifdim \dimen0 < \dimen1
      \repeat
    \global\StrainedGlue\dimen0
  \endgroup
}

% ============================================================ BOX ANALYSIS ====

% stashes for the data
\newdimen\FittedWidth
\newdimen\NaturalWidth
\newdimen\Deformation
\newdimen\MaxStretch
\newdimen\MaxShrink
\newdimen\OverStretch
\newdimen\OverShrink
\newcount\LineBadness
\newcount\Grayness

\def\AssessBox#1{
  \setbox0 = \hbox to \wd#1 {\unhcopy#1}
  \FittedWidth = \wd0
  \LineBadness = \badness
  \setbox0 = \hbox {\unhcopy#1}
  \NaturalWidth = \wd0
  \StrainGlue{shrink}{-}{minus}{0} \MaxShrink = \StrainedGlue
  \StrainGlue{stretch}{}{plus}{0}  \MaxStretch = \StrainedGlue
  \Deformation = \dimexpr\FittedWidth-\NaturalWidth\relax
  \OverStretch = \dimexpr\Deformation-\MaxStretch\relax
  \OverShrink = \dimexpr-\Deformation-\MaxShrink\relax
  \definecolor{Grayness}{rgb}{0,1,0}
  \ifnum \LineBadness = 1000000
    \definecolor{Grayness}{rgb}{1,0,0}
  \else\ifnum \LineBadness < 100
    \Grayness = \numexpr50\ifdim\Deformation>0pt+\else-\fi\LineBadness/2\relax
    \definecolor{Grayness}{gray}{0.\the\Grayness}
  \else
      \definecolor{Grayness}{rgb}{0,0,1}
  \fi\fi
}

% ========================================================= MARKERS DRAWING ====

\def\marker#1#2%
  {{\color{#1}\vrule width #2 height \ht\strutbox depth \dp\strutbox}}

\definecolor{OverShrink} {rgb}{1.0,0.0,0.0}
\definecolor{Shrink}     {rgb}{1.0,0.5,0.0}
\definecolor{Stretch}    {rgb}{0.0,0.5,1.0}
\definecolor{OverStretch}{rgb}{0.0,0.0,1.0}

% \tenthpt adapted from Michael J. Downes' showdim package
\def\tenthextract#1.#2#3\relax{#1\ifnum#2=0 \else.#2\fi}
\def\tenthpt#1{\dimen0#1\relax
  \advance\dimen0\ifdim\dimen0<0pt-\fi.05pt
  \expandafter\tenthextract\the\dimen0\relax pt}

\def\BadnessMarkers{%
  \llap{\smash{%
    \ifnum\LineBadness=1000000\relax$\infty$\else\the\LineBadness\fi%
    ~\marker{Grayness}{\baselineskip}}}%
  \ifnum\MaxStretch=\maxdimen\else\ifnum\MaxShrink=\maxdimen\else%
    \rlap{\hskip\hsize\smash{%
      \ifnum\LineBadness=1000000\relax%
        \rlap{%
          \marker{OverShrink}\OverShrink%
          \marker{Shrink}\MaxShrink%
          \marker{Stretch}\MaxStretch}%
        \llap{\tenthpt\OverShrink\hskip-4\baselineskip}%
      \else\ifnum\LineBadness>100\relax%
        \llap{%
          \marker{Shrink}\MaxShrink%
          \marker{Stretch}\MaxStretch%
          \marker{OverStretch}\OverStretch}%
        \llap{\tenthpt\OverStretch\hskip-4\baselineskip}%
      \else%
        \llap{%
          \llap{\marker{Shrink}\MaxShrink}%
          \rlap{\marker{Stretch}\MaxStretch}%
          \hskip\Deformation\relax}%
        \marker{white}{1sp}%
        \llap{\tenthpt\Deformation\hskip-4\baselineskip}%
      \fi\fi}}%
  \fi\fi%
}

现在,关于段落分析。根据您的具体情况,有多种方法来应用这些例程。

如果我们正在交谈,plain TeX那么我们可以做最大胆的事情并改变输出例程。这特别好,因为这是处理行间和段落间粘连的最简单方法。以下是plain.tex一个例子,可以玩弄:

\input miniltx
\input color.sty

\input badger

% code adapted from a TeX pearl by Paweł Jackowski (Custom overfull text)
% http://www.tug.org/TUGboat/tb29-1/tb91pearls.pdf

\interlinepenalty=-50000 % force the break between each two lines
\maxdeadcycles=50        % allow upto 50 \outputs with no \shipout
\newtoks\orioutput \orioutput=\output % wrap the original \output routine
\output
    {\ifnum\outputpenalty>-20000 \the\orioutput
     \else \ifnum\outputpenalty<-\maxdimen \the\orioutput
     \else
     \unvbox255        % flush the entire list back
     \setbox0=\lastbox % strip the very last box
     \nointerlineskip  % avoid doubled interline glue
     \AssessBox0
     \hbox to \FittedWidth{\BadnessMarkers\unhbox0}
     \advance\outputpenalty by 50000
     \penalty\outputpenalty % weak lie that nothing happened...
     \fi\fi}

\hsize=2in
\input knuth
\bye

我不会但是,使用 可以做到这一点LaTeX。但是,也可以局部应用分析程序。以下是la.tex,即生成上述屏幕截图的示例:

\documentclass{article}
\usepackage{color}

\input badger

% code inspired by an answer by David Carlisle
% https://tex.stackexchange.com/a/56853/82186

\newskip\savedskip
\newcount\savedpenalty
\newdimen\olddepth
\newbox\linebox
\newbox\parabox

\def\eat{
  \loop
    \setbox\linebox\lastbox
    \savedskip\lastskip\unskip
    \savedpenalty\lastpenalty\unpenalty
    \ifvoid\linebox\else
      \AssessBox\linebox
      \setbox0=\hbox to \hsize{\BadnessMarkers\unhcopy\linebox}
      \global\setbox\parabox\vbox{%
        \penalty\savedpenalty
        \vskip\savedskip
        \box0
        \unvbox\parabox}%
  \repeat}

\def\dissect#1\par{%
 \olddepth\prevdepth%
 \setbox0\vbox{\hbox{\vrule depth\olddepth}\par#1\par\eat}%
 \unvbox\parabox}

\begin{document}\vfil
{\bfseries A beautiful paragraph follows.}\par\vfil
\dissect\input zapf\par\vfil
{\bfseries Here comes an acceptable one.}\par\vfil
{\hsize=3in\dissect\input zapf\par}\vfil
{\bfseries Now the ugly.}\par\vfil
{\hsize=2in\dissect\input zapf\par}\vfil
\end{document}

这只是可能的方法之一。可以使用递归宏调用而不是循环。或者也许可以用于\everypar全局应用标记:那会很有趣!

请记住,我写的例子有点粗糙;我特别不喜欢第二个例子对段落间距的不完美处理。但是,分析是正确的,而且在我看来,大多数行级数据以这种方式都是清晰可读的。

我将尝试在某一天修复间距问题,以便可以随意地将其用作开/关绘图工具。

相关内容