PostScript的范畴目标和操作 作为对于桌面出书的文档描绘言语,PostScript的规划者力求要处理的核心问题,是怎么规划一个灵敏高效的言语,以操控桌面出书中各式各样的图形目标,并确保设备无关性。咱们无妨戴上言语规划者的眼镜,来模仿一下这个进程。 咱们面对的首要问题是怎么描绘桌面出书中的种种杂乱目标和操作。虽然任何平面出书物终究都是二维像素点的调集,但咱们并不期望这个言语局限于描绘像素点的色彩。这个言语最佳还能直接描绘文字、线条、形状等规划师了解的目标。由于从底子上讲,假设咱们要规划的描绘言语没有满足的表达才能,不能精简高效地表达图像、字体、形状、色彩等桌面出书范畴的事务目标,这个言语将不可避免地“难用”。通常来说,把范畴特定言语规划得“好用”,需求深沉的范畴常识(domain knowledge)。所幸的是,PostScript的规划者们,原先在施乐PARC从事激光打印机操控言语规划,对于桌面出书可算轻车熟路。因而,他们毫不费力地挑选了Bézier曲线、矢量字体、绘图途径(Path)等作为全部绘图体系的底子构造。在对这些目标的操作上,PostScript挑选了平移、旋转、放缩等仿射改换,加上途径操作和字体操控,构成了一个强壮但规整的绘图体系。 PostScript绘图体系的规划深入影响了后来的很多矢量图形体系。举例说,如今核算机运用的矢量字体均选用Bézier曲线描绘,即起源于PostScript;如今简直一切的矢量绘图言语都支撑的“途径”,也起源于PostScript。咱们不在此详细打开这些范畴目标挑选背后的因素,对PostScript感兴趣的读者能够阅览《PostScript Language Tutorial & Cookbook》(也称“Bluebook”)了解PostScript的一些底子概念。 PostScript的言语规划 底子范畴目标断定后,接下来便是力求规划出一个“灵敏高效”和“设备无关”的言语来操控这些范畴目标。规划目标落实为详细需求,包括以下三点。 榜首,言语自身要能表达曲线、字体、图像、形状等范畴目标;色彩、分页及这些目标的平移旋转等操作,在言语里最佳也都是一等公民,能直接表达。 第二,言语的表达才能要满足强壮,最佳是图灵彻底,以支撑灵敏的需求。 第三,言语要与设备无关,也便是说,言语将运行在一个虚拟机或解说器上,而非直接编译为二进制代码。 考虑到咱们要规划的言语是对于桌面出书的,终究还要加上一条:这个言语的语法和构造要满足简单,使得非编程专业人士也能运用。 有了需求的辅导,咱们不难了解PostScript所采纳的规划:以一个易用的、图灵彻底的言语作为蓝本,参加很多对于桌面出书的目标操作,并完成一个轻量的、与设备无关的解说器。事实上,PostScript是以FORTH言语作为蓝本规划的。挑选FORTH的主要因素,是由于它是一个轻量级的、根据栈虚拟机的言语。FORTH的表达才能和易用性其时已被实习所证实,因而借用它的底子操控语法便是一个很天然的挑选。 逆波兰表明法和衡量单位 逆波兰表明法是FORTH和PostScript等根据栈的言语的一个鲜明特点。在ALGOL宗族言语中,3乘以4的通常写法是3 * 4,即运算符中缀。PostScript将运算符后缀,写作“3 4 mul”。意思是将3、4别离推入栈中,然后将乘法(multiply)操作运用于两个栈顶元素(弹出),并将乘积结果入栈。FORTH依然选用+、*等数学符号。PostScript规范化了一切的操作符,共同选用add、mul等单词操作符来替代+、* 等传统的中缀操作符。咱们稍后将阐明规整化的优点。这儿只需求了解一点:PostScript程序实质上是一个后缀表达式。PostScript没有所谓的语法,只要栈操作。假设非要说有语法,那便是逆波兰表明法。这一点十分类似于LISP——所谓的语法,便是S表达式。 PostScript答应以闭包界说新操作符,其间,闭包是放在{}中的后缀表达式。例如,“乘以3”这个操作可界说为:/mul3 { 3 mul } def。这儿,/mul3表明取“mul3”的符号值。{ 3 mul }是一个闭包,而def将mul3这个符号,映射到{ 3 mul }闭包。据此,4 mul3即为4 3 mul。 本来,从语法上看,/mul3 { 3 mul } def和3 4 mul并没有显着的不相同:都是前两个操作元入栈,最终一个操作符进行运算。也便是说,PostScript的栈是异构的,符号、数字和闭包都能够放入栈中。很多操作符如if,也依赖于栈上有一个布尔值和一个闭包。这种不在栈中差异代码和数据的规划,答应咱们重写栈上的闭包。实际上咱们能够证实这个特性等价于LISP里的宏(Macro)的表达才能,限于篇幅,咱们不在这儿打开。 如今,咱们从mul3这个平铺直叙的比方出发,界说一个英寸(inch)的操作符:/inch {72 mul} def。一眼看去,{72 mul}是闭包,而inch是长度单位,两者毫不相干,为何强拉在一起?本来,PostScript的底子长度单位是1/72英寸,因而5 inch即打开为5 72 mul,或许说360个底子单位。Inch的界说使得咱们能够书写1.2 inch 2.3 inch moveto这么直观的程序。 用闭包界说常用衡量单位在PostScript中并不罕见。对于从未触摸过这种界说办法的读者来说,信任inch这个比方让人形象深入,由于它昭示了衡量单位的实质:衡量单位是后缀闭包。比方咱们说10美元时,已在自觉或不自觉地将“美元”单位替换成 {汇率 mul}闭包,换算成60元人民币等。实际上,任何衡量单位之所以能被咱们感知,都是由于咱们脑中的一个潜在后缀闭包的效果。在摄氏度体系下的人对华式温度没有感觉,或许仅触摸必定数量级范围内的人对大数字不灵敏,都是由于一个因素:咱们没有树立一个将不了解的单位或数量级转化为可感知的单位或数量级的闭包。 PostScript的运行时字典栈 除底子操控语法外,PostScript引进了对于图形处理很主要的两个底子数据构造:字典和数组。能够幻想,存有一系列点的数组能够表达一个字符的概括,而字典能够极好地表达一套字体。不仅如此,经过字典栈这个概念,PostScript具有了FORTH和别的栈言语所彻底不具有的动态特性。咱们依然以一个比方阐明。 咱们界说一个求直角三角形斜边长度的操作hyp,即/hyp { dup mul exch dup mul add sqrt } def(这儿dup表明重复栈顶元素,exch表明交流栈顶两元素,sqrt为平方根,读者能够自行验证这个函数的正确性)。 这儿,3 4 hyp得到5。 对解说器来说,咱们新界说的hyp与mul并没有实质的不相同(后缀表达式和规则化带来的便当)。解说器处理这些操作符时,无论是言语预先界说仍是用户界说的,不可避免地需求进行符号表查找。也许的差异仅是到不相同的符号表里查找。进一步说,一个叫inch的符号在没有进行符号表查找之前,咱们底子不能断定这究竟是一个变量,仍是一个闭包。 为了共同地处理符号表的查找操作,PostScript引进了字典栈(dictionary stack)的概念。字典栈是一个由解说器保护的栈,而栈中的元素则是作为符号表的字典。解说器启动后,体系字典systemdict中含有一切预界说操作符和变量,如add、mul等。用户字典userdict将包括自界说的操作符和变量。用户也能够随时树立新的字典插入字典栈中。 以字典办法存储符号表是简单了解的,但是为什么需求把这些字典参加“栈”中呢?本来,PostScript是按栈的次序在字典中寻觅操作符的。假设界说“/mul {add round} def”,则其时字典中的mul会被优先运用,而体系界说的mul不再可见。乍看之下,这和面向目标言语里提到的运算符重载概念类似。实质上,PostScript的规划要灵敏很多。 首要,由于字典栈的存在,每个运算符都主动有了效果域(预界说的运算符由于存在于systemdict中,从而有大局效果域)。经过字典栈,咱们能够完成别的言语中的lambda表达式或许Java中的匿名内部类。PostScript的运算符实质上是动态效果域的,但由于字典栈的存在,咱们能够轻松完成词法效果域,办法便是在效果域中暂时界说一个字典,在字典中界说新的操作符,并将字典推入字典栈。这么,只要在效果域结束时弹出暂时字典,操作符界说也随之吊销。很多PostScript程序都选用这种办法构建。 其次,字典栈奇妙地支撑了局部变量。和闭包相同,局部变量的实质是有效果域的值。根据栈的言语对函数局部变量是不友好的,由于局部变量自身是对处理器寄存器的笼统,拜访局部变量也是采纳随机存取而非按栈次序存取的办法。而栈机器自身不直接支撑寄存器笼统。了解JVM的读者都知道,JVM的{a,i,l,f,d}{load,store}系列指令,十分繁杂地支撑局部变量数组和栈之间的转存。在字典栈中,局部变量有了高雅的处理办法:经过树立暂时字典,咱们可在不引进杂乱的转存操作下,随机存取随机变量,而且局部变量的效果域得到了保证。比方,以下程序界说了一个叫做local_variable的局部变量,效果域仅限于/sample_proc。而将something换成{something}闭包,便是一个局部的操作符界说。 /sample_proc { 1 dict begin % 界说一个大小为1的暂时字典 /local_variable something def end % begin end 之间为字典元素 … % 详细的函数界说 } def PostScript和言语的Annotation 由于.ps文件实质是一个程序而非文档,打印PostScript文件的进程实质上是调用PostScript解说器履行程序的进程。由于PostScript的图灵彻底性,在PostScript程序履行完之前,咱们对文档的构造信息,例如总共多少页,文档有没有五颜六色元素等构造化的信息一窍不通。PostScript规划于桌面出书业没有起步之时,因而仅关怀制造操控,并未考虑到怎么表明这些构造信息,这么的缺憾是能够了解的。HTML言语也经过了这么的路途:前期引进FONT BIG这种纯展现标签,而如今最佳实习是将构造信息放入HTML,而将格局信息交给CSS。 由于PostScript的成功,不断增加的人期望作为桌面出书规范格局的PostScript能包括文档构造信息。比方说,假设打印办理体系能在将PostScript使命交给打印机之前知道文档的页数,就能够更好地调度打印使命,或按页面收取费用等。这些对于文档的构造信息并不影响页面的展现,却是文档不可或缺的一部分。 为处理这个问题,PostScript用户自觉地界说了一种经过注释表明文档构造信息的办法。例如,在一个10页的文档开始参加%%Pages: 10,每一页的开端参加%% Page N等。由于是注释,PostScript解说器能够挑选疏忽它,而别的程序则能够据此办理文档。很多桌面出书软件也采纳这么的办法写入作者、创立日期等信息。在强壮的需求和既定职业规范的驱动下,Adobe总算决议规范化这些用来表征文档构造的注释,发布了一系列的“文档构造约好(Document Structuring Conventions)”。之所以叫约好,是由于木已成舟,无法强行请求每个PS文档办理器或打印机都恪守规范。 DSC使得静态构造查看变得也许。前文提到,PostScript语法就一种后缀表达式,静态语法查看并没有意义,而正确性查看却又十分难。引进文档构造约好后,咱们就有条件查看一些束缚,比方在宣称的描绘一页的区块以内没有不合法的分页操作等。DSC不影响现有言语逻辑,却引进了新的语义正确性束缚。 DSC这种引进新的元信息以静态查看程序的语义正确性的思维十分有前瞻性。惋惜的是,由于了解PostScript的人较少,这么的思维没能在别的言语中完成。Java 5.0才正式引进了annotation的概念,用@override这么的符号协助编译器查看办法多态。Python 2.2引进classmethod、instancemethod等decorator以查看办法的界说,而C++近来才正式支撑annotation。这些比程序自身更笼统的元信息,不断增加地变成了主动剖析东西的辅佐。在Google,咱们选用一套线程安全的符号以协助编译器静态查看代码的线程安全性。一切这些都成提升开发功率的好辅佐。而PostScript,是我所知的榜首个以元信息束缚程序语义的编程言语。 别的一些风趣的前史 PostScript言语的前史很风趣也很能给人启示,限于篇幅我仅录几则。首要,PostScript本来和Smalltalk很类似。由于相同出自于施乐PARC的研究,PostScript言语个性受Smalltalk影响很大。比方闭包的规划,if和repeat语法的规划,简直便是Smalltalk的翻版,仅在运算符次序上有差异。 Adobe的几位创始人从PARC独立出来后,开始力求开发一套打印机操控言语。了解这几位创始人的Steve Jobs以为,这个言语最主要的使命不是操控打印机,而是制造高品质文档。在Jobs的推进下,Adobe才开发了这套能够支撑苹果其时正在开发的LaserWriter激光打印机产出高品质文档的言语——PostScript。从此,Adobe这家毫不起眼的小公司一举变成桌面出书革命最大的受益者。 由于PostScript言语灵敏杂乱,解说PostScript言语需求强壮的微处理器。为此,Apple LaserWriter携带了一颗12MHz Motorola 68000处理器。而同时期与之相连的Machintosh核算机携带的却是一颗8MHz Motorola 68000处理器。 打印机处理器比主机更强壮,用如今的眼光看真是难以想象的。桌面出书的革命来得如此之快,需求的核算才能如此之大,是全部个人核算机职业所没有预见的。或许,将来的3D打印技能或量子传输技能(Star Trek Transporter),会让这种状况从头呈现。 (责任编辑:好模板) |