EasyFlow基本系统介绍 从这一期起开始要介绍easyflow的基本程式,easyflow的基本程式非常的短,很适合用来了解一个排版系统的运作。以后将逐渐的在这个基础上加入各种不同的功能,希望能以easyflow完成一个简捷的核心,而使以后的功能都能以TCL巨集的形式出现。 王佑中 使用电脑来做编排版面的工作使得一本书的出版大为简化,它的页献不比发明活字排版来得低。因为有了电脑我们才得以用更低廉的价格买到书,否则以今日飞涨的人工费用,一本书少说也要千把银子才买得到吧!但是使用电脑后简单的连个人都可以直接在电脑前排出一本有模有样的书了。 使用电脑来排版基本上的概念和人工排版类似,但似有更多的变化。因为我们不再受限於字级,也不再受限於一些机械的格局,我们也更可以很容易的做出更多的特殊效果,如重叠或背景等。我们现在开始看一下easyflow所使用的排版观念,有适当的地方我会点出easyflow还少了些什麽东西。 EasyFlow的排版观念 easyflow把文件中每一个元素,包括每一个字、每一张图、每一个栏框,都看成是一个方块。所有的这些方块一个接着一个的被放在纸张上,当一行满了便放到下一行中,一个栏框满了便移到下一个栏框中继续填入,当一页中所有各式的栏框都满了则移到下一页。 但有些方块并不加入上述的过程中,它们被显示在纸面上固定的地方。例如我们经常希望插图被放在纸面的上方,或是在某些特殊的页上希望在纸张的正中央放入一个图形。页次是另一个特殊的例子,我们可能希望它被放在页底的正中央。这些方块的位置通常是被使用者,或是某些使用者提供的程式来决定它的实际上在页面上的位置,而不是像一般文字般由easyflow的排版引擎来决定。 ■排版、固定方块 所以方块可大略分成二种,一种是由排版引擎决定其位置的方块,我称其为排版方块。另一种是自行决定或是由TCL巨集决定位置的方块,我称其为固定方块。 固定方块会出现在其产生时被赋与的位置,而且就在产生它时的页上。而排版方块则会依周围情况的不同而改变其位置,但彼此先后的次序会被维持。也就是说在原始档中先出现的方块在排版时也会优先决定其位置,而其后的方块则一定在其后面。如果我们在原始档中先写一段文字,然后在这个文字后面插入一个表格,则不论前面的文字如何改,表格永远处於文字的后面,绝对不会因为在它前面文字做了修改而跑到文字的前面去。 但在排版方块和固定方块混合时会产生一个问题,它们可能会重叠在一起。因为固定方块永远固定在同一个位置,而排版方块会一个接着一个的填满整个版面。总有一天它们会重叠的,如何防止这种情况发生呢?这当然可以给每一个方块一个防止叠覆的属性,但随着方块的增加,效率一定会大减,因为我们每加入一个方块就必须检查它和所有有防止叠覆属性的方块有无重叠。 ■栏框(图例) easyflow让使用者自己负责防止重叠,它使用栏框的概念来防止重叠。使用者应负责将可能重叠的固定方块放在一个被标示为禁止进入的栏框内。这种栏框和其它栏框有重叠时,其它栏框在填入方块时会避过它。中间那个就是标示为禁止进入的栏框,在它所占的位置上是不能放入任何排版方块的,因为easyflow的排版引擎会避过这个区域。但固定方块的位置是由使用者自己决定,所以不受这个栏框存在的影响。 栏框除了上述的功能外,它最主要的用处是把纸面上的文字分成很多块。我们可以在纸面上定义很多的栏框,每一个栏框都可以独立的排入各种方块。这些栏框可以把纸面分成很多不同的方块区域,利用上述的禁止进入区域甚至可以定义一些不规则的区域。我们可以利用栏框定义一个二栏式的文件,也可以在主文中插入一些方块文章,或是一些后序发展等小文。 栏框可说是一般分别排版系统和文字列印系统的标准,像倚天等中文系统虽然也可以让使用者在印列时做很多花样,但它们没有栏框,所以不能叫排版系统。easyflow的栏框非常强大,你可以定义不限数量的栏框,甚至在每一页都使用不同的栏框。你可定义一个临时的栏框,当它被填满后就自动消失。你也可以如上的禁止进入栏框,栏框还可以用来计算某一串文字在填入纸面后所需的高度,透过这种功能你可以做出让一群文字确定在同一页的效果。这种栏框就有点像在TeX中的box。(在程式中可以看到,这种栏框有CT_BOX的属性) ■参数 easyflow提供了很多的参数让你控制版面的编排,以后相信还会越来越多。和TeX一样,easyflow提供了使用者由巨集中更改这些参数的机会。不同的是easyflow使用TCL做为内建的巨集,TCL虽然不像TeX的巨集那麽有弹性,但它却有更好的可读性。TCL的语法简单,不像TeX的巨集令即使是有经验的程式师也望而生畏。easyflow更利用一些可设定为TCL程序的回叫函数让使用者能更进一步的控制整个排版的过程,你可以定义一个换页函数,一但定义后easyflow每当换页时就呼叫这个函数,你可以在这个函数中更新页次,在页面底部放入表示页次的文字,或是更新索引和交互参考的索引等。easyflow另外提供了很多的回叫函数让使用者在特殊情况发生时做适当的处理。easyflow甚至提供了区块插入函数,你可以每一个方块插入时取得控制权,如此几乎没什麽事不可以做了。 所以构成easyflow的要素就是:●方块●栏框●TCL巨集●回叫函数●排版核心●系统参数下面我将更详细的讲解easyflow的每一个动作。 系统的启动 你可以想见easyflow在开始时应该做的工作应该是设定所有的系统变数。在easyflow中使用了非常多的整体变数来记录系统的工作状态,这有点违反模组化的原则,但可能是最简单的做法,这在后面可以看到,easyflow总是选择最简单的做法。 系统变数启始的动作都在可分二部份,一部份简单的变数在宣告时变给予初始值。而一些较复杂或可由组态档中读入的参数则在Init()中设定其初始值,当然也有二者均出现的情况,这些初始值保证即使没有任何体裁档存在系统也可以正常的运作。 在系统变数设定完后呼叫InitPage()函数输出Postscript的档头,它被用来定义一些Postscript巨集以供以后使用。这些程序主要的目的是缩短输出档的长度,例如在输出一个字元A时,我们可能会用:1200 1000 moveto (A) show 在(1200,1000)的地方显示A这个字元。但我定义了一个程序s来做这件事:(A) 1200 1000s 在档头中s的定义为: /s {movetoshow} binddef 如此有二个好处,一是输出的长度几乎少了一半,另一个是速度也增加了。因为Postscript只要读入一个字元s就可以知道要做些什麽事,我们用bind在一开始就让Postscript把s和movetobind二个函式系结在一起,所以在后面使用时便没有经过解释器编译的过程,你可以把它当作己被解释器编译成中间码,就像大多数解释器做的一样。接下来我们就将要进入系统的主回圈。 系统主回圈这个回圈很简单只有叁行的码: while(ch=GetChar(currentPage,in)){Page_addOneChar(currentPage,ch);} 它的动作很简单,就是由先前开启的输入档in中读入一个字元,然后交给排版引擎排入目前页中。不过这里的读入一个字元这个动作并不像表面上那麽简单,easyflow和TeX一样在读入的阶段就做巨集展开及控制字元解释的工作。easyflow提供了丰富的控制字元,它可以控制使用的字体及其大小,控制字元间矩、行矩及其它各种的系统变数。有关这部份的控制都在GetChar()中被处理,它会再呼叫DealWithControlCode来处理有关控制码的部份。 在GetChar()中只有一个地方值得一提,那就是自动走文模式的设定。所谓自动走文是指无论原始输入档每一行的长度如何,easyflow会试图将每一行填满,而使每一行的右端尽量对齐。如果你要使一行文字在行的中间便换行,你必须用二个连续的换行来达到这个目的。 说起来有些复杂,但在实作上很简单,我们只要忽略换行即可。但我们必须检查是否有二个连续的换行存在,因为那代表真正的换行,排版引擎会对换行字元做特殊的处理。以后我将会介绍莎士比亚式的排版规则,大部份的修改都是在这个函数中做。现在我们可以开始进入排版引擎之中了。 排版引擎 这个部份是整个程式的中心,但它也是将来做可能大幅度修改的地方。easyflow的排版规则目前还很粗糙,它没有做行的最佳化,它没有行的对齐模式,它的TCL回叫巨集定义也还不是很清楚,但我相信它在未来一年中将会得到很大的改进。不过即使是现在的状态,它也已比很多在DOS上自称是排版系统的程式强多了。 整个排版引擎都在column.c这个档中,档案出乎意外的小吧!这也证明了排版程式本身是很简单的一种程式,事实上即使给人复杂的TeX其排版引擎本身也是数千行的长度而已,easyflow所做的和TeX其实没有太大的不同,但少了一些东西。如glue,kern,penalty这些东西,这是因为easyflow没有行长最佳化的概念,这些东西以后应会加入吧!(虽然在中文中它们的重要性大减) 在column.c中最重要的函式是Page_addoneBlock(),它负责将各种不同的方块依照它的下列五个参数决定其x,y座标的位置。 w 方块的宽度 h 方块的高度 ws 方块水平方向的空白 hs 方块垂直方向上的空白 type 方块的种类 一个方块在进入这个函数之前必须将这五个参数设定好。不过我们必须注意,只有排版方块需要由这个函数来决定其(x,y)位置,固定方块是不必经过这个函数而是直接使用Page_addBlock将方块加入纸面上。 在这个档案中别有一个包装函数Page_addOneChar,这是前面主回圈中所使用的函数,它的功用是适当的设定字元方块,并且将它送给Page_addOneBlock去决定文字方块的位置。这个函数放在这里当然是有一点错误,但这是easyflow发展过程中留下的结果。 所以现在我们开始来看一下Page_addOneBlock这个函式的动作,这个函式中注解相当的多,读者应该不难了解程式的意义。在这里我先用虚拟码的方式展示其演算法: if是一个特殊方块then 直接加入串列中不做其他处理endif if不是一个换行换栏或换页方块then 找寻一个可以放入此方块的位置并设其为现在位置, 当找不到时变设其为栏框的右缘。endif if现在位置无法放入方块或是遇到一个换行换栏换页方块 then将游标移到下一行 if己经超过栏框底端then if此栏框不加入自动走文then return endif if不是换栏或换页方块then 此时最后一行的方块应放在下一栏内,将这些方块记成oldlist。endif 将栏框参数设回其初始值将目前栏框设为下一个栏框if找不到可用的下一个栏框或方块为换页方块then 此时应换页了,首见把页中所有的方块用ShipPage函数输出至结果档中。 将页中的栏框参数设为最后一栏框的参数 将所有暂时栏框删除 用ReadColumnFromStyleFile读入体裁档中下一个页的栏框,如果己无资料则直接使用目前页的栏框。endif 为新栏框插入一个字型定义方块将oldlist中的方块插入新找到的栏框中if不是换行换栏换页方块then 将目前方块插入栏框中endifreturnelse 找到了可以放入方块的位置后更改方块资料中的位置参数后呼叫Page_addBlock将方块插入。 endif 整个演算法可能还有些缺陷,我希望你可以告诉我你的想法。easyflow还是一个相当年轻的系统,我们还有很多的弹性可加入其中。你应该仔细核对一下上面的演算法和实际程式之间的关系,我现在只提出几乎加以说明。 (1)换行换栏换页都是以一个方块的形式出现,这是为了在多栏排版时能得到正确的结果。 (2)寻找下一个可能位置的程式是在SearchInALine中,方法很简单,虽然效率不是十分的好,不过这就是easyflow的哲学。永远选最简单的演算法,而不是最有效率的。 (3)当一行文字开始时,其游标位置是在文字的左上角,不过Postscript期望我们把文字放在其基准线上。所以我们把每一个方块分成二部份,一行的文字可看成 当方块被插入时其y座标都被放在A处,而当一行结束后会由下列这一段程式: nb=col->firstb;while(nb){if(nb->type nb->y-=col->maxHeight;nb=nb->next;} 将所有方块的y座标移到B处。而下一行的启始位置则在C。 (4)我们每排完一页就把所有方块清出,也就是把所有记忆体释出,所以能处理的文件大小几乎没有限制。 (5)上面演算法中省略了很多的TCL回叫函数的呼叫。 好了,了解easyflow的排版策略了吗?很简单是吧!我们现在就进入一些比较细节的部份。 Postscript图形的处理 EasyFlow最令人振奋的功能可能就属允许Postscript图形的插入了。因为这意谓着我们可以插入几乎任何的图形档,因为我们有很多免费的图形转换程式,如ImageMagic,xv...etc。它们都可以将几乎市面上可见的所有格式的图形档转成Postscript。不过这个功能的实作出奇的简单,因为我们原本的输出档就是Postscript,所以插人的工作不过就是适当的定义一个转换矩阵来缩放这些图形档而己。 EasyFlow可以插入二种不同类形的Postscript档,一种是EPSF档,一种是PS命令档。所谓EPSF档是指一种固定格式的Posrsctipt档,这种档中除了一般的Postscript命令外,多了一个所谓结构化注解,这些结构化注解使我们可以很容易的知道这个EPSF档的大小位置等属性,我们可以由这些属性决定如何定义我们的转换矩阵。 PS命令档则是指一般的Postscript档,easyflow处理这二种档时采用不同的方法。插入一个EPSF档时easyflow会在插入前先放入一个save命令,则在结束时放入一个restore命令。如 save100100translate0.30.3sacle........restore save是把所有目前的Postscript状态备份起来,而restore则是存回先前储存的状态。但在PS命令档的状况下就没有这二个命令了,也不会有第二行的转换矩阵定义。此时这个插入的档会影响实际的Postscript状态,这个命令通常不应被用来直接下达Postscript命令,而是被用来定义一些命令供后面的TCL巨集使用。在以后的文章中会更清楚的看到这一点。 而在EPSF的部份easyflow只使用BoundingBox来决定其大小,BoundingBox有四个参数。分别是整个图形的左上角和右下角的位置,由这四个参数我们可以求得其宽度和高度。如果左上角不在(0,0)而在(x,y),则我们须做一个(-x,-y)的translate使它回到我们想要的点,而easyflow允许用使用者定义一个图形的小大,所以我们也要用一个scale命令使图形缩放至指定的大小,整个处理过程都在EPSF.c这个程式中。 这一期就到此为止,我己经介绍过大多数的easyflow程式。剩下的就是TCL巨集的部份了,下一次我将介绍: (1) TCL的语法 (2) 如何为应用程式加上TCL巨集 (3) easyflow中的TCL巨集 (4) easyflow栏框的详细介绍 (5) easyflow-TCL的简单应用