PostScript入门(2)-基础概念
上一章,我们简单介绍了PostScript语言,以及如何在Windows、Linux上 运行PostScript。PostScript是个页面描述语言,也是个编程语言。 本章将介绍PostScript的语言基础概念和图形基础概念, 为以后的几章做个铺垫。
语言基础概念
从编程语言的角度来看,PostScript是十分简单的。PostScript是一种RPN, 即“逆波兰式”(Reverse Polish Notation),或称“后置记法”。 学过数据结构的人想必不陌生吧。简单来说就是操作数在前,操作符在后。 一般我们使用“中置记法”,比如 1 + 2,加号写在两个操作数1和2的中间; 但在PostScript中,要把加号放在最后,写成“1 2 add”。这是PostScript 的基础。
栈
后置记法最大的好处就是处理方便。学过编译原理的同学应该不会忘记
中置记法中的表达式歧义问题带来的痛苦吧(比如处理1+2*3
,就必须用某种
方式告诉计算机是先计算1+2
还是先计算2*3
),但在后置记法中就不会有这个问题。
只需用一个栈,依次处理表达式中的符号,’‘遇到操作数就压栈,遇到操作符
就从栈顶弹出相应数量的操作数进行计算,并把计算结果压栈。’’
例如,要计算(1+2)*(7-4)
,用PostScript表示如下:
1 2 add 7 4 sub mul ==
末尾的“==”表示显示栈顶元素。这行代码可以直接在GhostScript的提示符下运行, 结果如下所示:
GS>1 2 add 7 4 sub mul ==
9
下面来解释一下运行过程:
处理的符号 | 操作 | 栈的内容(右边为栈顶) |
1 | 压栈 | 1 |
2 | 压栈 | 1 2 |
add | 栈顶两元素出栈,计算加法,计算结果压栈 | 3 |
7 | 压栈 | 3 7 |
4 | 压栈 | 3 7 4 |
sub | 栈顶两元素出战,计算减法,计算结果压栈 | 3 3 |
mul | 栈顶两元素出栈,计算乘法,计算结果压栈 | 9 |
== | 栈顶一元素出栈并显示 | 空 |
可见,这种方式完全不需要对表达式进行语法分析,所以PostScript解析器 可以做得相当简单。实际上,PostScript语言中的确有这样一个栈, 用于存放操作符和操作数并进行计算,称为“操作数栈”。
在GhostScript中,栈的大小会显示在操作数之后。例如,可以逐个地 输入上述表达式(PostScript语言中,空白和换行是没有意义的, 所以一条命令可以分成几行写,也可以写在同一行上,效果相同):
GS>1
GS<1>2
GS<2>add
GS<1>7
GS<2>4
GS<3>sub
GS<2>
“GS”后面的数字就是栈中的操作数个数。“pstack”命令可以查看栈的内容, 例如在上面 GS<2> 的状态下执行结果为:
GS<2>pstack
3
3
另外,这里的“操作数”和“操作符”的概念也可以理解为“参数”和“函数”, 计算后的结果可以认为是函数的“返回值”。
数据类型
PostScript中的数据类型有数字、字符串、数组和过程。
数字
PostScript支持整数和实数。整数部分为0的小数可以省略整数部分,如:
.123 % 表示 0.123
此外还有进制计数法和科学计数法。进制计数法写成“基数#值”,例如
16#ff % 表示16进制的0xff,即255
8#777 % 表示8进制的0777,即511
7#33 % 连7进制都可以使用
2#3 % 出错,因为2进制中没有3这个符号
科学计数法写成“尾数E指数”,例如
1E10 % 表示1e+10
1.234E-2 % 表示0.01234
字符串
字符串用圆括号表示,例如
(abcdefg) % 表示字符串"abcdefg"
(a(b)c) % 表示字符串"a(b)c",圆括号内成对的圆括号失去特殊意义
(a\(bc) % "a(bc",单个圆括号需要转义
(a\nb) % "a<换行>b"
转义符的形式有:
\t | TAB符号 |
\n | 换行(line feed) |
\r | 回车(carriage return) |
\f | 换页(form feed) |
\b | 退格 |
\( | 左圆括号 |
\) | 右圆括号 |
\\ | 反斜杠 |
\ddd | 用八进制表示的字符 |
注意转义符中没有十六进制表示(\xnn
)。
此外,也可以直接在字符串中加入换行:
GS>(abcd
efg)
GS<1>pstack
(abcd\nefg)
如果想在代码中让字符串换行,但并不想在字符串中加入换行, 可以在行尾使用 \ 符号:
(This is a \
string \
that has no \
newlines)
的值为
(This is a string that has no newlines)
字符串还可以用十六进制表示,只需将十六进制数写在尖括号中。例如:
<616263> % 表示"abc"
<6d 6e 6f> % 表示"mno",中间加入空格或换行不起作用
布尔值
PostScript支持 true 和 false 两个布尔值。例如:
GS>true false
GS<2>pstack
false
true
数组
数组是一系列操作数的排列,用方括号括起来。数组中的数据可以是任何类型, 甚至可以是另一个数组。
[ 367 28.4 (abc) true ] % 四个不同类型元素的数组
[ 1 2 add 7 4 sub ] % 值为[3 3]。数组中甚至可以做运算,数组值为运算结果
[ 21 [ 53 74 ] [ 60 [ 53 48 ] 99 ] 18 ] % 可以任意嵌套
[ ] % 空数组
过程
过程是一系列操作符和操作数的排列,放在大括号中,用于自定义操作符。 例如:
{ dup mul } % 计算平方。dup表示复制栈顶元素,mul表示栈顶两元素相乘
实际应用中需要使用def操作符将过程定义到名字(见下文)上,如:
GS>/square { dup mul } def % 定义 square 为 { dup mul }
GS>3 square == % 调用 square
9
字典
字典是一系列成对的名字和值,相当于Perl语言中的哈希表,或者Java中的HashMap。
名字
名字是任何不是数字的符号,相当于一般编程语言中的标识符。
名字可以由除了空格和特定保留字符((、)、[、]、<、>、{、}、/和%)以外的
任意字符组成,例如abc
、$def
、ghi-jkl
都是有效的名字。
名字甚至可以以数字开头,例如1Z
是有效的名字,但这种习惯有时会遇到问题
(如1E10
是个实数)。
通常,名字出现时会被当作操作符,PostScript会去“调用”该名字对应的过程。
那么怎样给名字定义“过程”呢?这就需要使用“字面意义的名字”(literal name)
和def
操作符。在名字前面加上 / 符号,可以告诉PostScript,我们要定义这个名字了,
别把它当作操作符,当作操作数压进栈里就行了。def 操作符则从栈顶弹出两个元素,
并把第二个元素(字面意义的名字)定义为第一个元素(过程)。例如:
/square { dup mul } def % 定义 square 为 { dup mul }
3 square % 调用 square,相当于 3 dup mul
这里还要说明一下,所谓“调用”并不是像C语言那样跳转到函数定义的位置去执行。
PostScript中的过程实质上是一系列的操作符和操作数,在“调用”
一个名字时,PostScript简单地用该名字对应的那个过程来替换该名字,
然后依次执行。例如上面的3 square
,PostScript在遇到square
时,
从以前的定义中找到square
相当于 { dup mul }
,于是将square
“展开”
成dup mul
,再依次执行,其效果跟直接写3 dup mul
是相同的。
注释
PostScript中使用 % 符号表示注释,% 之后的内容会被忽略。
图形基础概念
作为页面描述语言,PostScript的主要功能就是绘图和显示文字,那么有几个 图形方面的基础概念是必须要了解的。
用户空间
用户空间是PostScript用于表达点和线的位置的坐标系。它的原点(0, 0)位于左下角, x轴向右,y轴向上,相当于数学中的第一象限。
用户空间的默认单位是点(point)。一个point相当于1/72英寸, 这个值比真正打印机上的point(等于1/72.27英寸)稍稍短一些。
实际应用中,可以作以下定义,以便在PostScript程序中使用英寸、毫米等比较直观的单位。
/mm { 2.834646 mul } def % 毫米
/inch { 72 mul } def % 英寸
1 inch 20 mm moveto % 应用例
路径
路径是页面上一系列直线和曲线的组合。它并不是可见的线条,只是假想的一条路径而已。
通过stroke
和fill
操作符可以描边或填充路径(用过Photoshop就能理解),使之可见。
图形状态
图形有许多状态(如当前颜色、当前路径、线的粗细、坐标系等)。
程序有时需要临时改变图形状态,此时应该将当前的图形状态保存到图形状态栈(graphics state stack)中,
画完之后再恢复状态。保存和恢复可以通过gsave
和grestore
操作符来实现。