在第 103.3 节“画布转换”中,TikZ 和 PGF 手册 3.0.1a 版指出了以下区别:坐标变换和画布变换(第 1051 页):
与坐标变换不同,PGF 不会“跟踪”画布变换。具体而言,当使用画布变换时,它将无法正确保存形状或节点的坐标。
然而,当你查看 PGF 命令的源代码时,例如\pgfshadepath您会看到它使用了系统层变换(又称画布变换)。这难道不危险吗?因为它会禁用 PGF 正确保存形状和节点坐标的能力?
我还尝试编写将 PGF 层命令与系统层转换相结合的代码,生成的图片并没有变得混乱。相反,它看起来完全符合预期。例如:
\documentclass{article}
\usepackage{pgf}
\begin{document}
\begin{pgfpicture}
\makeatletter
% A system-layer rotation by 90 degrees counter-clockwise
\pgfsys@transformcm{0}{1}{-1}{0}{0cm}{0cm}
\makeatother
% A PGF-layer rectangle that is thin and tall.
\pgfpathrectangle{\pgfpointorigin}{\pgfpoint{1cm}{2cm}}
\pgfusepath{stroke}
\end{pgfpicture}
\end{document}
其结果如下:
那么,在什么意义上 PGF 不跟踪画布变换,并且在使用这种变换时无法正确保存形状或节点的坐标?
答案1
前列腺素F/钛钾当应用画布变换时,Z 会失去坐标的轨迹,尤其是边界框的轨迹。
考虑以下代码:
\documentclass{article}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
\makeatletter
\pgfsys@transformcm{0}{1}{-1}{0}{0cm}{0cm}
\makeatother
\filldraw [blue] (0,0) rectangle (1,2);
\draw (current bounding box.south east) rectangle (current bounding box.north west);
\end{tikzpicture}
As any dedicated reader can clearly see, the Ideal of practical reason is a representation of, as far as I know, the things in themselves; as I have shown elsewhere, the phenomena should only be used as a canon for our understanding.
\begin{tikzpicture}
\begin{scope}
\makeatletter
\pgfsys@transformcm{0}{1}{-1}{0}{0cm}{0cm}
\makeatother
\filldraw [blue] (0,0) rectangle (1,2);
\end{scope}
\draw (current bounding box.south east) rectangle (current bounding box.north west);
\end{tikzpicture}
As any dedicated reader can clearly see, the Ideal of practical reason is a representation of, as far as I know, the things in themselves; as I have shown elsewhere, the phenomena should only be used as a canon for our understanding.
\begin{tikzpicture}
\filldraw [rotate=90, blue] (0,0) rectangle (1,2);
\draw (current bounding box.south east) rectangle (current bounding box.north west);
\end{tikzpicture}
As any dedicated reader can clearly see, the Ideal of practical reason is a representation of, as far as I know, the things in themselves; as I have shown elsewhere, the phenomena should only be used as a canon for our understanding.
\begin{tikzpicture}
\begin{scope}
\filldraw [rotate=90, blue] (0,0) rectangle (1,2);
\end{scope}
\draw (current bounding box.south east) rectangle (current bounding box.north west);
\end{tikzpicture}
As any dedicated reader can clearly see, the Ideal of practical reason is a representation of, as far as I know, the things in themselves; as I have shown elsewhere, the phenomena should only be used as a canon for our understanding.
\end{document}
有时,如果你迷失了方向也没关系。给路径着色通常就是这种情况。你需要跟踪路径。你不一定需要跟踪阴影。由于 Ti钾Z 将变换后的阴影剪切到未变换的路径,只有在计算边界框时路径才重要。
答案2
PGF 变换与最终图片无关。PGF 试图做的是向 TeX 报告正确的框大小。否则,PGF 会像往常一样将路径软路径指令转换为特定于驱动程序的特殊指令。
在这里,画布变换意味着 PGF 不会考虑生成的对象大小,它所称的危险是结果可能会超出它向 TeX 报告的框大小(包括对象位置),因为变换将由驱动程序执行,而 PGF 不会管自己的事。如果结果仍在边界框内,那就好了,如果不是,那就太倒霉了。
除了所有这些步骤之外,转换是 PostScript/PDF 特有的。它与 TeX 端无关。
答案3
以下内容旨在通过提供更详细的解释来补充 cfr 的答案。
当我描述 TikZ/PGF 的“幕后”工作原理时,这并不意味着要将其理解为对源代码的严格指导,而是一种非正式的、但希望对用户感知的行为有帮助和启发性的解释。套用《教科书》的话来说,这篇文章并不总是说实话,但作者认为这种故意撒谎的技巧实际上会让你更容易理解这些想法。
我要指出的一个特别不准确的是“引擎”一词的使用。当我谈到“PGF 层引擎”或“系统层引擎”时,我指的是概念上的,而不是技术上的,有点像面向对象程序中的对象。实际上,只有一个引擎在排版 TeX 文档,那就是pdftex
,或者是它的一个兄弟引擎。
A)PGF/TikZ 包的分层设计
TikZ/PGF 包的结构分为多个层次:1. 系统,2. PGF,3. TikZ。每个层都独立于以下层。因此,系统层对 PGF 一无所知,而 PGF 使用系统层,但对 TikZ 一无所知。每个层都根据层来实现直接地其下层是系统层仅有的一个与渲染驱动程序“对话”(例如,当 LaTeX 引擎为pdftex
/时,为 PDF 驱动程序pdflatex
),PGF 层是根据系统层实现的,而 TikZ 是根据 PGF 层实现的,并且从不直接访问系统层。这些层可以共存:PGF 图片可以包含系统层命令,而 TikZ 图片可以同时包含 PGF 和系统层命令。当来自不同层的命令混合在一起时,每个命令将由处理相应层的“引擎”唯一地处理。现在我们将注意力集中在两个较低的层上:系统层和 PGF 层。
一个层中所做的更改不会自动传播到另一层,并且没有事件机制使一个层能够实时“监听”另一层。此外,这些层永远不会查询另一层的状态;每个层都保持自己的状态,这就是仅有的声明它曾经访问过。
因此,在 PGF 图片中写入的系统层命令是无形的到 PGF 引擎。当在 PGF 图片上下文中执行系统层命令时,其效果将发生(例如,将在系统层引擎中安装转换),但 PGF 引擎不会知道此命令已执行,也不会知道此命令对系统层引擎的影响。PGF 引擎只能看到 PGF 层命令和其自身的状态。
这回答了 OP 的问题:“PGF 在什么意义上不跟踪画布变换?”
接下来我们将看一个例子,但是为了能够正确解释它,我们首先需要描述 PGF 图片如何在页面上呈现。
B)PGF 图片如何在 PDF 页面上呈现
在 PGF 图片生命周期内生成的系统层命令(无论是用户直接指定的命令还是 PGF 层命令生成的命令)都不会立即发送到 PDF 驱动程序进行渲染,而是被记录下来(用 PGF 的术语来说,是协议化的命令)并保存起来,以便在图片完成后重放。这样做的原因是图片在页面上的位置取决于边界框的最终大小和 PGF 坐标,而这些只有在图片的所有命令都执行完毕后才能确定。
PGF 层使用的坐标系是抽象的,不引用物理位置。另一方面,系统层(或者更准确地说是 PDF 引擎,但这里可以安全地忽略区别)使用的坐标系引用要在其上呈现图片的当前物理页面,方式如下:坐标系的原点位于页面的左下角,x 轴沿页面的下边缘增加,y 轴沿页面的左边缘增加。这些是系统层的初始设置,但可以通过执行系统层转换来更改它们。
当 PGF 图片完成并准备渲染时,将确定边界框左下角在页面上的物理位置的系统层坐标 (x,y),然后执行系统层变换以将系统层坐标系的原点移至 (x,y)。最后,执行协议规定的系统层命令序列以在页面上渲染图片。
图片渲染完成后,将恢复正常的 TeX 处理。下一个字母将位于与图片边界框下边缘长度相等的位置,该距离位于边界框左下角在页面上所映射位置的右侧。
C)一个例子
现在,我们来看一个例子。考虑以下 LaTeX 手稿。
\documentclass{article}
\usepackage{pgf}
\usepackage{lipsum}
\begin{document}
% 1st paragraph: Only text, no picture.
\lipsum[1]
% 2nd paragraph: Text preceded by a PGF picture. No transformations.
\begin{pgfpicture}
% A rectangle whose lower-left corner is at the origin,
% and upper-rigth corder is at (1cm,2cm).
% The rectangle is filled, but its circumference is not stroked.
\pgfpathrectangle{\pgfpointorigin}{\pgfpoint{1cm}{2cm}}
\pgfsetfillcolor{blue!20}
\pgfusepath{fill}
% The picture's bounding box is stroked.
\pgfpathrectangle{\pgfpointanchor{current bounding box}{south west}}
{\pgfpointanchor{current bounding box}{north east}}
\pgfusepath{stroke}
\end{pgfpicture}%
\lipsum[1]
% 3rd paragraph: Text preceded by a PGF picture.
% The picture is rotated 90 degrees counter-clockwise
% using a canvas transformation.
% The rectangle is filled, but its circumference is not stroked.
% An attempt is then made to stroke the picture's bounding box
% using PGF-layer commands, while the canvas transformation
% is still in effect.
\begin{pgfpicture}
% Allow the character '@' to be a part of a macro's name.
\makeatletter
% Install the canvas transformation matrix
% 0 1
% -1 0
% which represents a rotation by 90 degrees counter-clockwise.
\pgfsys@transformcm{0}{1}{-1}{0}{0pt}{0pt}
% Restore original '@' settings.
\makeatother
% A rectangle is painted as in paragraph #2 above.
\pgfpathrectangle{\pgfpointorigin}{\pgfpoint{1cm}{2cm}}
\pgfsetfillcolor{blue!20}
\pgfusepath{fill}
% An attempt is made to stroke the picture's bounding box
% using the same command as the one used in paragraph #2 above.
\pgfpathrectangle{\pgfpointanchor{current bounding box}{south west}}
{\pgfpointanchor{current bounding box}{north east}}
\pgfusepath{stroke}
\end{pgfpicture}%
\lipsum[1]
\end{document}
生成的 PDF 文档如下所示(文档的边距已被剪掉):
D)示例解释
第一段没有嵌入 PGF 图片,因此我们可以看到段落第一行自然开始的位置。
第二段以一个矩形的 PGF 图片开始,该矩形的左下角位于原点,右上角位于 (1cm,2cm)。该图片是用纯 PGF 编码的,没有应用任何变换。矩形已填充,但其周长未勾勒出来。看到的描边边界是图片的边界框,该边界框是明确描边的。图片的边界框是一个 PGF 概念。系统层没有这样的概念。PGF 引擎会跟踪边界框的状态。
第三段也是以 PGF 图片开头。图片的代码与第二段图片中使用的代码几乎相同,只是在开头插入了系统层逆时针旋转 90 度的代码。执行此命令时,系统层引擎中会安装相应的变换矩阵。但是,如 A 节所述,以下 PGF 代码对此一无所知。对于 PFG 引擎而言,就好像没有这行代码一样。
现在 PGF 绘制一个矩形。就 PGF 而言,绘制的矩形与第 2 段中绘制的矩形相同;具体来说,它没有任何旋转,其坐标为左下角的 (0,0) 和右上角的 (1cm,2cm)。但是,当 PGF 引擎将矩形的坐标发送到系统层引擎时,系统层引擎会根据安装的旋转来解释这些坐标。因此,矩形将被渲染为“侧放”,但就 PGF 引擎而言,矩形是“直立”的。
接下来,描边边界框。如第 A 节所述,边界框是 PGF 概念,而不是系统层概念,PGF 引擎仅根据其自身状态来计算边界框。此时的状态与第 2 段中执行图片时计算边界框的状态相同。因此,生成的边界框与那里计算的边界框相同,即沿着矩形的圆周运行。因此,PGF 现在向系统层引擎发送命令,以绘制一个与之前绘制的矩形具有相同坐标的矩形。与以前一样,系统层根据安装的旋转来解释这些坐标,因此生成的矩形轮廓也将“在其侧面”呈现。与以前一样,就 PGF 引擎而言,图片的边界框是直立的:其左下角位于原点,其右上角位于 (1cm,2cm)。
现在图片的范围已经到达了尽头,图片已准备好进行渲染。如 B 节所述,根据边界框的 PGF 坐标和边界框左下角在页面上的物理位置,执行系统层变换以将系统层坐标系的原点(最初位于当前物理页面的左下角)移动到页面上的正确点。由于 PGF 引擎的状态与第 2 段图片末尾的状态相同,因此系统层坐标系的原点将移动到与“Lorem ipsum”文本在没有当前图片的情况下的位置重合。
现在图片已渲染完毕,TeX 处理恢复正常,下一个字母的渲染距离等于图片边界框下边缘的长度,该长度位于页面上边界框左下顶点的物理位置的右侧。由于当前 PGF 引擎的状态与第 2 段图片末尾的状态相同,因此该长度将与那里的长度相同。