之前我们了解了PDF文档的基本结构,并且展示了一个简单的hello world。这个hello world 虽然只在页面中显示一个hello world 文字,但是包含的内容却是不少。这次我们仍然以它为切入点,来了解PDF的坐标系统以及坐标变换的相关知识
中学我们学习了平面直角坐标系,x轴沿着水平方向从左往右递增,Y轴沿着竖直方向,从下往上坐标递增。而PDF的坐标系与数学中的坐标系相同。但是PDF的坐标是有单位的,PDF的坐标单位为磅,一般来说他们与英寸等的转化关系为 1 磅 = 1/72 英寸 因为PDF需要做到设备无关,也是就是在不同的显示像素和打印机上,显示的长度都一致,所以这里不能采用像素做单位。但是我们可以通过相关的接口来将这个单位转化为像素。例如在Windows平台可以通过下列的代码来获取一英寸有多少像素
HDC hdc = GetDC(NULL);
short cxInch = GetDeviceCaps(hdc, LOGPIXELSX);
short cyInch = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(NULL, hdc);
对于我的显示器来说,水平和竖直方向都是 1英寸=96像素
有了这些概念之后,我们来看一个例子,下面是在页面的(200, 200) 位置画一个 长宽都为100的正方形
3 0 obj % 页面内容流 << >> stream % 流的开始 200 200 100 100 re S endstream % 流结束 endobj
之前说过,页面显示内容在页面流中,因此这里我们将内容放置到页面流对象中。前面的200 200 是矩形的起始位置。后面的100 100 分别是长和宽。re 代表我们要构建一个矩形,最后的S表示要显示这个图形。严格意义上来说,re 和S都是路径构造所使用的操作符。这里的矩形也不单单是一个图形,它是一个路径。关于他们的概念将在后面继续介绍。下面我们来介绍基本的2D图形变换
假设一个点原始坐标是(x1, x2),那么沿着x轴平移a,y轴平移b,那么平移之后点的坐标为 (x1 + a, x2 + b) ,转换成矩阵就是
利用中学的知识可以知道 $$ x_1 = r * cos(\theta+\psi) = r(cos\thetacos\psi-sin\thetasin\psi) = xcos\theta-ysin\theta $$
同理,我们可以得到 $$ y_1 = r * sin(\theta+\psi) = r * (sin\thetacos\psi+cos\thetasin\psi) = xsin\theta+ycos\theta $$
转换成矩阵就是
缩放就是将坐标扩大或者缩小为原来的多少倍,我们可以很清楚的知道 $$ x_1=xa y_1=yb $$
这里的a和b都是缩放的系数 利用矩阵表示就是 $$ \begin{bmatrix} x & y & 1\end{bmatrix} \begin{bmatrix} a & 0 & 0 \ 0 & b & 0 \ 0 & 0 & 1\end{bmatrix} $$
还有另外几种变换,这里就不一一列举了。现在我们知道二维图形的变换使用一个矩阵就能进行描述。所以PDF在变换图形的时候直接使用的是变换的矩阵。另外我们观察到对于二维变换来说,最后一列一直都是 0 0 1这三个数字。所以pdf中设置变换矩阵时忽略最后一列,仅仅保留前两列,采用6个数字
这个矩阵在PDF中表现为 a b c d e f。
回到我们之前hello的例子中,我们在 hello world 字符流开始的时候,给定了几个数字 1. 0. 0. 1. 50. 700. cm 各个数字之间采用空格隔开,这里数字后面跟的点表示它是一个浮点数。我们可以将这一列数字写成如下的矩阵
这个矩阵我们叫做当前变换矩阵 (Current Transformation Matrix CTM),最后的cm表示使用该矩阵进行图形变换。它是current matrix
的缩写
所以上述这一串数值的意思就是将 hello world 这个字符串平移到页面坐标 (50, 700) 的位置
现在我们利用这个上述知识来做一个小练习。我们将一个长宽都为100 的矩形在 (200, 200) 位置逆时针旋转45° 绕任意点旋转,可以先将该点移动到坐标原点,然后按照坐标原点的进行旋转的公式进行计算,最后再将坐标点平移回原来的位置。这个过程产生3个变换矩阵
平移矩阵
旋转矩阵
平移矩阵
我们将这三个矩阵相乘
最终得到这样一个矩阵
3 0 obj % 页面内容流
<< >>
stream % 流的开始
200 200 100 100 re S %原始矩形
0.7 0.7 -0.7 0.7 200 -80 cm%进行坐标变换
200 200 100 100 re S %变换后的矩形
endstream % 流结束
endobj
这样我们可以得到如下所示的图形
这个时候我们会发现,同样是(200, 200) 的位置,在变换前和变换后,得到不一样的图形,这就说明我们的坐标系统被改变了。不再是水平和竖直方向的x y轴了。如果我们想要它变回原来的位置该怎么办?
在GDI或者其他框架的图形编程中,在改变画笔、画刷等图形状态的时候,会首先保存原来的,然后更新,最后再还原。同样在PDF中,也存在有这样的保存和还原的操作符。我们使用q/Q这么一对操作符来完成保存和还原的操作。
我在原来的基础上,再加一个矩形,在(400, 400) 位置画一个长宽都是100的矩形
3 0 obj % 页面内容流
<< >>
stream % 流的开始
200 200 100 100 re S %原始矩形
0.7 0.7 -0.7 0.7 200 -80 cm%进行坐标变换
200 200 100 100 re S %变换后的矩形
400 400 100 100 re S % 这个矩形是相对于 (200, 200) 这个点旋转了45°的矩形
endstream % 流结束
endobj
我们再采用q/Q这一对操作符来保存和还原图形状态
3 0 obj % 页面内容流
<< >>
stream % 流的开始
200 200 100 100 re S %原始矩形
q
0.7 0.7 -0.7 0.7 200 -80 cm%进行坐标变换
200 200 100 100 re S %变换后的矩形
Q
400 400 100 100 re S % 这个矩形是相对于 (400, 400) 这个点旋转了45°的矩形
endstream % 流结束
endobj
这个时候我们发现它已经在(400, 400) 这个位置画了一个矩形。没有任何的图形变换
PDF中将图形状态保存成一个栈结构,每次执行q就是将当前图形状态进行入栈,使用Q将之前保存在栈顶的图形状态进行出栈,并还原成当前图形状态。一般来说q/Q必须成对出现。 好了,本节到这里就结束了。本节主要介绍了图形变换矩阵以及PDF中变换矩阵的操作符cm以及q/Q 这一对保存和还原图形状态的操作符