《More Programming Pearls》在第九章介绍了一种名为pic的小型语言(little language),它和graphviz一样也是贝尔实验室的产物,作者是大名鼎鼎的K!所谓小型语言,就是你可以在一小时内学会并使用之,下面我们争取能达到这个目标。
pic也是troff的预处理器,Linux都会自带这个小工具。它的作用仅仅是把一段文本中标识符.PS
和.PE
之间的指令,即pic语言,解释成troff语言,最终实际是由troff绘图。另外还有两个类似pic的预处理工具是eqn和tbl,顾名思义分别是用作绘制公式和表格的。这里有一份关于troff家族的全家福。
pic语言的基本理念是:
- 想象在图纸上有一个绘图的光标;
- 逐行读入指令,光标按照指令从(from)当前位置向(to)某方向移动或绘图;
- 下一条指令如果不明确重置光标,则是在上一条指令的光标位置和方向的基础上继续执行,直到绘图结束。
先给一个十边形连通图的例子:
用如下代码绘制:
.PS
pi = 3.1415926; n = 10; r = 1; s = 2*pi/n
for i = 1 to n-1 do {
for j = i+1 to n do {
line from r*cos(s*i), r*sin(s*i) to r*cos(s*j), r*sin(s*j)
}
}
r1 = r+0.1
for i = 1 to n do {
sprintf("%g", i) at r1*cos(s*i), r1*sin(s*i)
}
.PE
pic是以英寸为绘图单位的,下面是各种形状的默认大小:
形状 | 大小 | 形状参数 |
---|---|---|
box | 3/4”宽 x 1/2”高 | width, height |
circle | 1/2”直径 | diameter |
ellipse | 3/4”宽 x 1/2”高 | width, height |
arc | 1/2”半径 | radius |
line/arrow | 1/2”长 | up, down, left, right |
move | 1/2”平移距离 | up, down, left, right |
记住每绘制完一个形状后光标所处的位置,有助于更好的使用pic绘图。
下边是一些预定义变量的默认尺寸值:
boxwid = 0.75; boxht= 0.5
linewid = 0.75; lineht= 0.5
circlerad = 0.25; arcrad= 0.25
ellipsewid = 0.75; ellipseht= 0.5
movewid = 0.75; moveht= 0.5
textwid = 0; textht= 0
arrowwid = 0.05; arrowht= 0.1 (These refer to the arrowhead.)
dashwid = 0.05; arrowhead= 2 (Arrowhead fill style)
maxpsht = 8.5; maxpswid= 11 (Maximum picture dimensions)
fillval = 0.3; scale= 1
pic默认每行是一条指令,也可以使用分号分隔各条指令。如果使用大括号{}
,则表示其中指令执行完毕后,不改变光标的位置和方向;如果使用中括号[]
,则表示其中的形状是一个整体,称作block。
一条指令可以用于定义一个形状,例如:
line up 1 right 2
arrow "on top of" above
box invis "input"
box dotted height 0.2 width 0.2 at 0,0
box same
arc -> from 0.5,0 to 0,0.5
arc -> cw from 0,0 to 2,0 radius 15
使用 ljust
、rjust
、above
和 below
可以修改文字的默认位置。
pic中也可以使用标记(label)来引用一个形状,不过为了变量区分,标记需要大写字母开头。例如:
Box1: box + 1,1
pic中形状有边角点(corner)的概念,分为8个方向点,外加中心点,分布是东 .e
、南 .s
、西 .w
、北 .n
、东南 .se
、东北 .ne
、西南 .sw
、西北 .nw
和中心 .c
。边角点一般搭配标记使用以获取坐标,例如,Box1.se
表示该形状右下角那个点的坐标。边角点也可以搭配with
属性使用以绘制形状,例如,box with .sw at 1,1
表示以左下角的坐标绘制形状。
使用 1st
、2nd
、last
这样的标识可以用来引用相应的形状,包括block,甚至是引用形状的边角点。例如,last box.nw
表示获取上一个box形状的左上角坐标
from
、to
指令通常是搭配线条形状使用的,不过此时如果直接连接形状的话,默认使用的是形状的中心坐标,所以一般还会搭配chop
属性使用,这样会从形状的边界开始连线。比如:
arrow from 1st circle to 2nd circle chop
pic可以说是面向对象的,每个形状都是一个对象,且有各种成员属性。例如:
Box1.x # the x coordinate of the center of Box1
Box1.ne.y # the y coordinate of the northeast corner of Box1
Box1.wid # the width of Box1
Box1.ht # and its height
2nd last circle.rad # the radius of the 2nd last circle
last [].A # label A of last block
同时提供如下内置函数:
sin(expr), cos(expr), atan2(y,x) # angle in radians
log(expr), exp(expr) # Beware: both base 10
sqrt(expr),max(e1,e2 ),min(e1,e2)
int(expr) # integer part of expr
rand() # random number between 0 and 1
pic中甚至还有宏,主要用于替换文本,而且宏可以有可选参数。例如:
define square { box ht $1 wid $1 $2}
宏还可以搭配文件操作使用。例如,文件里逐行保存着一些坐标点,可以用如下语句绘制这些坐标点:
copy /path/to/file thru { "." at $1, $2 }
pic还支持for循环和if判断。例如,绘制一条分段函数:
pi = atan2(0,-1)
for i = 0 to pi by 0.1 do {
if (s = sin(i)) > 0.8 then { s = 0.8 }
"." at i/2, s/2
}
不过pic里不处理各种字体、字号等内容,而是交由troff处理,所以,如果对文本格式有要求,需要进一步参考troff的语法。
troff里处理文本格式常用的一些标记有:
\f
表示字体。比如,\fB
表示粗体,\f(BI
表示粗体+斜体,\fR
表示标准的Times Roman。\d
和\u
分别表示文字下沉和上升,可以用来表示上下缀。比如,X\d1
会显示为 X1。\s
表示字号。比如,\s12
表示12pt,而\s0
表示恢复到之前的字号,也可以用\s+2
表示增大字号。
由于eqn是可以预处理公式的,因此,也可以在pic里使用公式。语法和TeX类似,但命令无需反斜杠开头,具体可参考eqn手册。示例:
box "$space 0 {H( omega )} over {1 - H( omega )}$"
不过由于eqn只处理.EQ
和.EN
之间的文本,所以,需要利用所谓的内嵌公式功能,需要在文本开头声明内嵌文本标记,如下:
.EQ
delim $$
.EN
运行如下命令即可画出带公式的图形:
$ cat foo | pic | eqn | groff > foo.ps
更详尽的内容可以参考manual,一共才20来页,囊括了pic的所有内容。此外,别忘了我们同样也可以开发pic的预处理器,比如chem这个小工具就是可以解释一段化学公式描述,然后再交由pic处理,MPP这本书里还有其他一些例子值得我们思考学习。
参考
- Brian W. Kernighan, PIC - A Graphics Language for Typesetting User Manual (1991)
- 很全的troff资源 Troff Resouces