TEX学习笔记
欢迎加入本人建的"LuaTeX ConTeXt 学习互助"群:431714622,互助学习LuaTeX ConTeXt LaTeX相关技术,以及通过Lua、Python实现格式化文本、数据的自动排版。
主要学习如下资料的笔记:
- TeX by Topic, A TeXnicians Reference, by Victor Eijkhout, 2019
- The TeXbook, by Donald E. Knuth, 2021
- TeX Reference Manual, by David Bausum, 2002
盒子 boxes#
数字#
- \number,转换一个数字⟨number⟩()为十进制样式。
- \romannumeral,转换一个正数⟨number⟩为小写罗马样式。
- \ifnum,测试两个数的关系。
- \ifodd,测试一个数字是奇数。
- \ifcase,枚举case语句。
- \count,计数寄存器前缀。
- \countdef,定义一个与\count寄存器同义的控制序列。
- \newcount,分配为使用的\count寄存器。
- \advance,算术命令,从一个数字变量⟨numeric variable⟩加或减。
- \multiply,算术命令,乘以一个数字变量。
- \divide,算术命令,除一个数字变量。
数字和⟨number⟩#
在TEX中,⟨number⟩严格指整数。 而一般所说“数字”,有时指字符串形式的数字。
尺寸Dimensions和胶Glue#
在TEX中,垂直和水平的空格(space)可以通过 "拉伸 "(stretching)或 "收缩 "(shrinking)来调整自己。一个可调整的留白被称为胶。
- \dimen,尺寸寄存器前缀。
- \dimendef,定义一个与寄存器\dimen同义的控制序列。
- \newdimen,分配一个未使用的尺寸寄存器。
- \skip,Skip寄存器前缀。
- \skipdef,定一个与寄存器\skip同义的控制序列。
- \newskip,分配未使用的skip寄存器。
- \ifdim,比较两个尺寸。
- \hskip,插入一个水平胶。
- \hfil,等于\hskip 0cm加1fil。
- \hfilneg,等于\hskip 0cm加-1fil。
- \hfill,等于\hskip 0cm就1fill。
- \hss,等于\hskip 0cm加1fil减1fil。
- \vskip,插入一个垂直胶。
- \vfil
- \vfill
- \vfilneg
- \vss
- \kern,在当前水平或垂直列表中增加一个字间。
- \lastkern,如果当前列表最后一个项目是字间,取其尺寸。
- \lastskip,如果当前列表的最后一个尺寸是胶,取其尺寸。
- \unkern,如果当前列表最后一个项目是字间,将其移除。
- \unskip,如果当前列表最后一个项目是胶,将其移出。
- \removelastskip,宏,附加负的\lastskip。
- \advance
- \multiply
- \divide
1 2 3 4 5 6 7 8 9 10 |
|
胶⟨glue⟩和尺寸⟨dimen⟩的定义#
尺寸余论#
胶余论#
坏度 badness#
拉伸或收缩一个列表时,TEX会计算坏度,基于实际拉伸量与行中表示的拉伸总量之间的比例。坏度用于段落算法。
一个列表的坏度公式:
为效率起见,实际算法有简化。
\badness记录了最近构造的盒子的坏度。
胶和断breaking#
TEX可以在一个胶之前断行、断页,然后此胶被丢弃。
在Plain TEX中有两个宏,\hglue和\vglue,分别在水平和垂直模式下提供不消失的胶。对于水平的情况,实际放置的是:
\vrule width 0pt \nobreak \hskip ...
因为TEX在胶的前面断开,这个胶会一直附着在条线(rule)上,因此不会消失。实际的宏定义要复杂一些,因为它们要注意保留 \spacefactor 和 \prevdepth。
字间 \kern#
字间相当于不能伸缩的胶,但不是合法断点。比如:
.. text .. \hbox{a}\kern0pt\hbox{b}
以上盒子间不能断开;
.. text .. \hbox{a}\hskip0pt\hbox{b}
以上盒子间可以断开。
不过,如果字间后跟一个胶,字间处可以断开(假设不是数学模式)。在水平模式中,字间和胶都在断开时消失。在垂直模式下,断点前的材料被输出例程处理,它们被移动到(空的)当前页,于是被丢弃。
胶与模式modes#
所有水平skip命令都是⟨horizontal command⟩,所有垂直skip命令都是⟨vertical command⟩。这就是说,比如\hskip用在垂直模式中,TEX就会开始一个新段落。\kern命令可以用在两种模式中。
列表尾胶:尾空backspacing#
列表中的最后一个胶是可以测量的,可以移除,除非在外部垂直模式下。内部变量 \lastskip 和 \lastkern 可以用来测量所有模式下的最后一个胶;如果最后一个胶不是skip或kern,它们分别给出0pt。在数学模式下,\lastskip的功能是⟨internal muglue⟩,但在一般情况下,它归类为⟨internal glue⟩。
如果测的是列表最后的glue或kern,\lastskip和\lastkern也是0pt。
\unskip和\unkern操作,分别移除列表最后的glue和kern。它们在外部垂直模式下无效;此时最好的替代方法是\vskip-\lastskip和\kern-\lastkern。
在段落构造过程中,TEX自身执行一个重要的\unskip: 一个以空白行结尾的段落会被TEX的输入处理器插入一个空格token。在插入 \parfillskip 胶之前,它将被 \unskip 移除。
胶会被TEX当做铅条(leader)的一个特例,对铅条应用\unskip时自然会删除铅条。
尾空的例子#
宏\removelastskip的定义是:
\ifdim\lastskip=0pt \else \vskip-\lastskip \fi
如果列表的最后一个条目是胶,这个宏会用它的值加尾空,加入它的自然尺寸不是0。否则,什么也不加。
跟踪输出中的胶#
- \tracingoutput
- \showbox
- 当overfull或underfull时
盒子的呈现形式(比如用\showbox)会在显式kern后加空格,但不会在字体tfm文件插入的隐式kern后加空格。所以,“\kern 2.0pt”表示用户或宏插入的kern,“\kern2.0pt”表示隐式kern。
比如:
\vbox{\hbox{Vo}\hbox{b}}
跟踪输出为:
\vbox(18.83331+0.0)x11.66669 .\hbox(6.83331+0.0)x11.66669 ..\tenrm V ..\kern-0.83334 ..\tenrm o .\glue(\baselineskip) 5.05556 .\hbox(6.94444+0.0)x5.55557 ..\tenrm b
段落形状#
\hsize 用来排版段落的行宽,包含\leftskip、\rightskip、\parindent、\hangafter(n行后悬挂)、\hangindent等。
\parshape⟨equals⟩ n i1 ℓ1 . . . in ℓn,针对前n行每行的缩进(i)和行长度(l)。多余n行时,重复使用最后一组数据。少于n行时,忽略多余数据。指定\parshape后\hangindent无效。
断行#
"坏度"(badness):TEX用这个概念来决定如何将一个段落分成几行,或者在哪里断页。
各种罚点,如连字,形成断行成本。
- \penalty,罚点,表示不在此处断行的意愿强度。
- \linepenalty 与每次断行相关的罚点。默认为10。
- \hyphenpenalty 通常情况下,在一个连字符处断行的罚点。默认值为50。
- \exhyphenpenalty,当断点前文本为空时,在水平行连字符条目处分行的罚点。默认50。
- \adjdemerits,毗连行的视觉不协调的罚点。默认10000。
- \doublehyphendemerits,连续以连字符结束的行的罚点。默认10000。
- \finalhyphendemerits 倒数第二行有连字时加入的罚点。默认5000。
- \allowbreak,插入\penalty0,生成断点。
- \pretolerance 一个无连字符段落的坏性容忍的值。默认100。
- \tolerance 一个无连字符段落中的每行的坏性容忍值。默认为200。
- \looseness 超过理想行数的行数(可以是负值)。
- \prevgraf 最近加入垂直列表的段落的行数。
\prevgraf 必须仅仅在两次断行之间有效,断行时/在段内则被重置,无法取值。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
当盒子、图片、MP在行首时会自动切换到v模式,从而导致自动断行。可以前置 \dontleavehmode
or \quitvmode
以阻止这种行为(\nobreak
和\penalty10000
等是事后加胶以控制断行算法,此处无效);
或cheat:前置0宽胶\hskip\zeropoint
or \hskip0pt
。
另参考\leavevmode
\ifvmode
等
段落的断行成本计算#
分行构造一个段落,为的是与断行相关的总扣分d最小。一个段落的总扣分值是各个行的扣分值的总和,再加上一些可能的额外罚点。将一个段落作为一个整体来考虑,而不是逐行断开,可以导致更好的断行:TEX可以选择在段落的开始部分取一个稍微不那么漂亮的行,以避免以后出现更大的麻烦。对于每一行的扣分是由拉伸或收缩到断行处的坏度b和与断行有关的罚点p计算出来的。坏度不允许超过某个预先设定的容许值。除了对单个行的断行进行扣分外,TEX还对行的组合方式进行扣分。
TEX段落分行算法的实现,参见:D.E. Knuth and M.F. Plass. Breaking paragraphs into lines. Software practice and experience, 11:1119–1184, 1981.
坏度/坏性 Badness#
根据行中表示的拉伸或收缩与实际使用的拉伸或收缩之间的比值,算出在某一点断行的坏度。坏度是断行的重要因素。
坏度也用于断开垂直列表,这里仅讨论断行。
描述坏度的术语:
- tight (3),紧,指任何收缩行坏度 b ≥ 13,即至少使用了收缩量的一半。
- decent (2),正,指任何行的坏性 b ≤ 12。
- loose (1),松,指任何拉伸行坏度 b ≥ 13,即至少使用了拉伸量的一半。
- very loose (0),很松,指任何拉伸行坏度 b ≥ 100,即尽数或超额使用了拉伸量。请记住,胶可以拉伸超过许可量,但不能收缩超过许可量。
该计数用于跟踪输出,也用于以下定义:如果相邻两行的分类号数相差超过1,则称这两行视觉上不协调(visually incompatible)。与此相关的\adjdemerits参数见下文。
如果发生溢出的水平盒子和垂直盒子的宽度或高度分别小于 \hfuzz 或 \vfuzz,则忽视;如果坏度小于 \hbadness 或 \vbadness,则不报告。
罚点Penalt和其他断开位置#
水平列表的可能断点(breakpoints):
- 在罚点penalty处。罚点是此处断行的美学代价(aesthetic cost)。负罚点被看做红利。罚点10000或更多,则禁止断行;罚点−10000或更少,则强制断行。连续放置一个以上的罚点,相当于只放置一个最小值的罚点,因为这个罚点是断行的最佳候选。水平模式下的罚点是由用户(或用户宏)插入的。唯一的例外是在\parfillskip胶之前插入的\nobreak。
- 在胶glue处,如果胶不是数学公式的一部分,并且前面有一个不可丢弃(non-discardable)项。在胶处断行是没有惩罚的。不可丢弃的前项这个条件是必须的,否则可能在两个胶之间断行,这将导致段落的边缘出现锯齿。
- 在字间kern+胶glue处, 如果字间不是数学公式的一部分。在字间处断行没有罚点。
- 在数学关(math-off)+胶glue处。因为“数学关”(和“数学开”,math-on)用作字间,这很像上一条。在“数学关”处断行没有罚点。
- 在词语断字(discretionary break)处。其罚点是\hyphenpenalty或\exhyphenpenalty。 后当论及。
任何可丢弃材料,跟随在断点——胶、字间、数学开/关、罚点——之后时,将被丢弃。如果你知道在胶(kern, math-on/off)处断行实际发生在胶的前端,这就意味着这个胶在断行中会消失。
缺点 Demerit#
假设l是\linepenalty值,b是行的坏性,p是断点的罚点,那么行缺点d:
段落的缺点是各行缺点的总合,再加上:
- 所有\adjdemerits(毗连行不协调罚点)
- 所有\doublehyphendemerits(连续断词行罚点)
- 和\finalhyphendemerits(倒数第二行断词罚点)
段落的行数#
\prevgraf,可以与悬挂缩进、段落形状等联合使用,以影响TEX的决定过程。
行间#
段落机制把行打包为水平盒子,然后添加到垂直列表中。结果的垂直条目序列如下重复:
- 一个盒子,包含一行文本;
- 可能的迁移来的垂直材料;
- 一个罚点条目,反应在此处断页的代价,通常是\interlinepenalty;
- 行间胶,自动计算自\prevdepth。
断行处理#
TEX试图以这样的方式来分割段落,即每行的坏性(badness)不超过一定的容忍值(tolerance)。如果存在一个以上的解决方案,那么就取其缺点(demerits)最少的一个。
通过设置\tracingparagraphs为正值,可以使TEX在日志文件中报告段落机制的计算结果。一些TEX的实现可能会禁用这个选项以使TEX运行得更快。
三遍#
一遍,尝试不不断词的断行成段,如果每一行的坏性都没有超过\pretolerance 则成功。
否则二遍,插入断词断点,应用\tolerance。如果\pretolerance是负值,则忽略一遍。
如果一、二遍都失败,还可以让TEX做第三遍。如果\emergencystretch是正值尺寸,TEX将应用这么多的额外拉伸可能性于每一行,据此计算坏性和缺点。哪些较少超过容忍值的解决方法,此时被选用。不过,并没有使用一个胶来表达\emergencystretch的尺寸,因此盒子不满的信息可能会出现。
容忍值#
TEX排版一段文字时会遇到多少麻烦,部分基于容忍值。因此明智的是,稍微了解坏性在视觉术语中的意义。
对于拉伸后的行,坏性是100乘以拉伸率的立方。因此坏性800意味着,拉伸率是2。 如果空格如下,以10点Computer Modern字体为例,
3.33pt plus 1.67pt minus 1.11pt
坏性800意味着所有空格被拉伸为:
3.33pt + 2 × 1.67pt = 6.66pt
就是说,结果正好是2倍自然尺寸。由你判断这是不是太大了。
段落结束#
TEX会在段落的最后一个元素后隐式插入\parfillskip的等价物:
\unskip \penalty10000 \hskip\parfillskip
\unskip用于移除段末的假胶,比如由行尾生成的空白,当\par由输入处理器插入时。
\parfillskip在纯TEX中是一级无限胶(first-order infinite,0pt plus 1fil),所以段末是\hfil$\bullet$\par
时,bullet将处于最后一个词语到行末的半中间;段末是\hfill$\bullet$\par
时,则被冲到右边。
空格#
TODO#
\relax#
命令解析终止标记,以免继续解析造成歧义(有些命令还能接收更多参数)。
glue 胶#
TEX中的内容都以盒子的形式存在,水平盒子之间由胶连接,还有其他的元素,构成水平列表;水平列表再如此构成垂直列表。
Glue如同弹簧一样,有三个属性:自然空间(space);拉伸(stretch)能力;收缩(shrink)能力。只有当行、列版面糟糕的时候才会按比例拉伸和收缩。拉伸值最终可能被系统突破,而收缩值不会被突破。glue一旦设定(setting)后,就不变了。
- \smallskip,等于\vskip3pt plus1pt minus1pt,或\hskip3pt plus1pt minus1pt
- \medskip,2*\smallskip
- \bigskip,2*\medskip
- \vskip⟨glue⟩,glue:⟨dimen⟩ plus⟨dimen⟩ minus⟨dimen⟩,自然值必须指定,加减值默认为0
- \hskip
- \enskip,等于\hskip.5em\relax
- \vfil,无限扩展胶,等于\vskip0pt plus \vsize
- \centerline,居中,两端加无限制胶
- \vfill、\vfilll,与\vfil共用时,拉伸强度不同
- \vss、\hss,无限扩展和收缩胶
- \hfil、\hfill、\hfilll
- \hfilneg and \vfilneg取消\hfil and \vfil的效果
- fil、fill、filll作为尺寸,\vskip 0pt plus 1fil minus 1fil
标点后的glue有特殊规定。
行间距:
- \baselineskip=⟨glue⟩
- \lineskip=⟨glue⟩
- \lineskiplimit=⟨dimen⟩
空白系数 space factor f
水平列表开头的空白系数是1000,在一个非字符盒子或数学公式推送到列表上之后,也会再设置为1000。
可以这样指定\spacefactor=⟨number⟩
空白系数。不过,一般是在简单字符盒子(simple character box)加入列表时才设置为非1000的值。
每个字符都有空间系数代码g,进入列表时会设置为当前空间系数。但是,如果g是0,则f不变;如果f < 1000 < g,则空间系数将设置为1000。
(也就是说f不会从1000之下一步跳到1000之上。)空间指数最大是32767。
重载字体词间距与伸缩:
- \spaceskip,伸展f/1000倍,收缩1000/f倍
- \xspaceskip,当前space factor f >= 2000时使用(space factor f 通常是1000)
- \sfcode‘)=0 \sfcode‘.=3000,个别字符的空间系数指定
- \frenchspacing重置为1000
指定盒子的伸缩(相对于自然宽度):
\hbox spread 5pt{‘‘Oh, oh!’’ ... laughed.}
\kern 字间#
字间类似glue,但不能伸缩,不会作为断行点。
\line 行#
\line{}生成宽度为\hsize的行。
\mark 标记#
主要供参考书类出版物,比如词典,用于排印页面顶端的引导词。可用的相关原语:botmark, firstmark, topmark, splitbotmark, splitfirstmark.
\insert 插入#
插入vbox材料,诸如脚注、尾注、浮动体。
protect 保护#
最佳实践:当你发现奇怪的错误出现在一个会移动的参数(即一段文本,会移动到文档的其他部分,比如脚注会移动到页脚,标题会移动到目录,等等)之中或附近,那就尝试\protect其中的所有宏。这可以解决99%的问题。