注:本文是《The advanced TeXbook》(作者David Salomon)一书第19章(Insertions)的部分译文。(note #1)是章节的尾注。
19. 插入对象(Insertions)
插入对象(Insertions)被认为是TeX中最复杂的主题,许多人掌握了比如标记、文件输入输出、宏甚至输出例行程序之后才着手学习插入对象,原因在于插入对象确实很复杂,尽管The TeXbook涉及到了这一主题的所有方面,但是这部分仍然写的比较隐晦,缺少一些简单的例子。原书讨论插入对象主要出现在页[115-125],这里还讨论了TeX的寄存器,页[363-364, 423-424]给出的插入对象例子几乎没有解释,因此,本书有必要加入这一章。本章试图解释插入对象的细节,特别是展示一些简单的例子,由浅入深,步步展开。
19.1 主题导入
定义:一个插入对象是文档的一部分,在某个位置生成后,能出现在文档的其他地方。
插入对象一些常见的例子是脚注、尾注(note 1)还有浮动体。这些都是很重要的特性,它们解释了为什么要在TeX中纳入一般的插入机制。原书[124]说道:“这个算法诚然是复杂的,但是好像没有比这更简单的了。”使用插入对象,就可以把一些材料(文本或图片等)储存在盒子里,并在文档的任何地方排版出来。这些素材可以插入到当前页面,也可以放入缓存,插在之后的页面,还可能被分割开,出现在当前页和后继页面,或者等到文档结束了再插入。plain格式提供了一些方便的宏来控制脚注和浮动插入,他们都基于一般的插入机制。
放置在页面右侧的索引条目是插入对象的一个好例子,它是manmac format[附录E]的一部分,可参看(note 2)来了解它的概述,简单版本在19.8有叙述。
必须指出,尽管插入对象比较常规也很复杂,但他并不能处理所有能想到的情况。比如facing figeures
(note 3),这是一个TeX插入机制无法解决的问题。但通过其他的方法(note 4)却很容易实现。
19.2 简单例子
在深入插入对象的细节之前,抛开已有的特性,从零开始建立一个示例讲很有用。我们将为浮动插入对象建立一个简单的机制。假设一个图表要放到文档的某个位置(在排版之后)。我们需要为每一个图表预留好空间,这可以通过在每个插入点放vbox
来实现。
练习19.1 为什么不能简单的用vskip...
或者kern...
在页面中预留垂直空间?(note 5)
因此我们定义了一个宏Pic,defPic#1 high{parvbox to #1{}}
,使用时候输入Pic 3.5in high
,当然,问题在于,在当前页面上可能没有额外的3.5英寸空间,这种情况下,插入对象应该“浮动”到下页的顶部。这样,我们需要扩展下这个宏,以便于在构造vbox
之前,先测量下当前页的剩余空间。为了理解如何完成测量,读者应该复习下16章的pagetotal
和pagegoal
,16章还构建了宏pagespace
,下面是这个宏的定义。
newdimenspaceleft
defpagespace{%
ifdimpagetotal=0pt
spaceleft=vsize
else
spaceleft=pagegoal
advancespaceleft by -pagetotal
fi}
我们现在写一个宏Pic
,根据需要先设置box0
为一个空的vbox
,然后比较图片的高度和页面上可用的高度,如果有足够的空间,box0
就正常排版,在页面上为图表预留好空间;否则,box0
就附加到fig
这个盒子里。
newboxfig
defPic#1 high{%
setbox0=vbox to #1{}
pagespace
ifdim#1>spaceleft
setboxfig=vbox{
unvboxfignointerlineskipbox0}
else
box0
fi}
几次调用Pic
后,要么boxfig
是空的,要么它包含了一些中间没有间距的垂直盒子。接下来启动输出例行程序时,它首先输出当前的页面,然后检查boxfig
。如果这个盒子是非空的,输出例行程序会通过unvboxfig
清空它,并把它的内容放到MVL的顶部,显示在下一页的顶部。
output={shipoutbox255 advancepageno
ifvoidfigelse unvboxfigfi}
照这样,下一页顶部就能为尽可能多的图表保留足够的空间,写入unvboxfig
,而不是写boxfig
很有必要。因为在MVL这个位置的,不是单一的一个盒子boxfig
(它本身不可见),而是它所包含的内容,比如一些分割开的盒子。如果这些内容包含了许多元素,那么可能已经把这些元素分割到不止一个页面上了。
细心学习这个简单的例子,它为我们完全理解插入对象提供了一个极佳的起点。
19.3 插入对象(概述)
首次阅读这部分时,应略过尾注。
TeX所使用的的插入对象机制基于盒子寄存器(参见[122-125]),当分配到一个盒子寄存器之后,insert
命令用于向盒子(note 6)中累加内容,最终把垂直项目排版到同一个页面或者其文档的其他地方。如下所述(note 7),使用标准的特性,输出例行程序可以把盒子排到此页任何位置。
例如:命令newinsertfig
分配了盒子寄存器boxfig
。每次使用命令insertfig{<vertical material>}
都会向盒子中存入项目,TeX假定这些项目最终将由输出例行程序排到文档的某个位置。如果项目被排到当前的页面,为了在这页保留空间,TeX将会(参看388页关于countfig
的讨论)按垂直项目的尺寸减少g(note 9)。
译者注:g表示pagegoal
,t表示pagetotal
,388页是19.4节的内容。
只有在输出例行程序启动前,插入对象的盒子才可用(note 10)原书页[254]说道:“在输出例行程序开始前,插入对象被放入自己的盒子中。”输出例行程序能通过如下的一些结构来排出boxfig
中的项目:
shipoutvbox{box255unvboxfig}
把插入对象排到页面底部。shipoutvbox{unvboxfigbox255}
排到页面顶部。shipoutvbox{vsplit255 to 4in boxfig box255}
排到页面顶部4in之后。shipoutvbox{rlap{kernhsizevbox to0pt{boxfigvss}}box255}
把插入对象排到页面顶部右边的空白处(right margin)。
19.4 插入对象(中级)
TeX实际执行的步骤会更加复杂,执行命令inserfig
时,它会将其中的材料放入缓存,而不是放在插入对象的盒子里。只有在输出例行程序开始前,缓存中那些适合于此页的项目才会放入插入对象的盒子里。注意,使用者有时会用setboxfig=vbox{unvboxfig <material>}
附加一些东西到插入对象的盒子,累积项目里最终也会有这些东西。当输出例行程序在页面上排版这个盒子时,盒子的所有内容输出到页面;然而,页面上预留的空间也只有insertfig
命令所设定的那么多。
上面提到的newinsert
命令不只是分配一个盒子。它实际分配了一个类插入对象(a class of insertions),类包括了count
、dimen
以及粘连(skip
)寄存器,他们都有相同的编号,并且初始值都是0,例如,上边的newinsertfig
,实际上预留了寄存器boxfig
、countfig
、dimenfig
以及skipfig
。这些称之为类插入对象fig
。如果fig
刚好被指定为编号100,那么上面的newinserfig
就分配了寄存器box100
、count100
、dimen100
和skip100
。
因为box255
在输出例行程序中有特殊用途,所以分配了0-254的类插入对象。宏newinsert
从254之前取一个数字,然后分配相应数字的盒子、计数、尺寸、粘连等寄存器。从254之前分配编号而不用255是因为box255
被输出例行程序保留为特殊用途,从高往低分配是因为,寄存器count0、count1...
用于储存页码,并且很多人倾向于用寄存器box0、box1...
临时储存些东西。
dimenfig
的值限制了在单一页面里所插入项目的尺寸,执行dimenfig=8in
,那么TeX在每一页上最多放置8英寸的来源于当前缓存的插入项目,如果当前缓存了超过8英寸的项目,那么超出的部分将放到下一页。把缓存中8英寸的项目放到boxfig
里,意味着TeX需要切分插入对象。切分工作由vsplit
(note 11)完成,这一操作也可在一般情况下使用。如果dimenfig
没有被用户设定,那么它的值就是0,这意味着在页面没有给插入对象留下空间。除非修改dimenfig
的值,不然就不取用插入项目,只是把它累积在缓存中。
寄存器countfig
根据g应减少的量来赋值,设置countfig=250
将导致g减少在boxfig
中的每个插入项目高度(加上深度)的25%。19.3节例4则应该设置countfig=0
,因为我们把它放置在了页右侧的空白处,并不需要留给它额外空间。
寄存器skipfig
设置了使用者希望输出例行程序在插入对象上方或下方放置的垂直间距,在有类fig
插入项目的页面中,TeX会根据skipfig
的值来缩减g的量,在页面上为粘连留出空间。但是粘连本身并不会自动插入页面,别忘了在输出例行程序里把总的skipfig
垂直粘连加进去。
19.5 追踪插入对象(初步)
理解插入对象一个比较好的方法(TeX其他很多方面也是如此)是去追踪相关量的值,用message
就很容易做到,它能显示运行时的许多内部量。下边这个简单的试验显示了插入对象工作时的诸多讯息。
hsize=3in vsize=100pt
output={shipoutvbox{
unvbox255 vskipskipfig unvboxfig} advancepageno}
newinsertfig
countfig=1000
dimenfig=vsize
skipfig=6pt
message{1:t=thepagetotal; g=thepagegoal}
Text for the first paragraph
message{2:t=thepagetotal; g=thepagegoal}
insertfig{Material}
Text for the second paragraph
message{3:t=thepagetotal; g=thepagegoal}
insertf ig{Material}
....
bye
这个简单的试验如果用tracingpages=1
重做,可以得到更加详细的信息,这些信息会显示TeX(实际上是建构页面的程序)是如何处理插入对象的。(详尽的例子可以看19.15节)