pgfextra 与 graph:一个悖论!

pgfextra 与 graph:一个悖论!

考虑以下 LaTeX 手稿,其中包含两个实心圆圈的 TikZ 图片。右边的圆圈画在 内\pgfextra。路径的填充颜色为黄色!

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
    \begin{tikzpicture}
        \path[fill=yellow] (-.5,0) circle(1)
            \pgfextra{\fill (.5,0) circle(1);};
    \end{tikzpicture}
\end{document}

它呈现为

                                                \pgfextra

如果我们现在\pgfextra用替换graph

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{graphs}
\begin{document}
    \begin{tikzpicture}
        \path[fill=yellow] (-.5,0) circle(1)
            graph{""[circle,at={(.5,0)},minimum size=2cm,fill]};
    \end{tikzpicture}
\end{document}

我们得到图片

                                                   图形

现在这里有一个悖论。

在两个例子中,路径具有相同的形式:

\path[fill=yellow] (-.5,0) circle(1) ...;

人们期望通过以下两种方式之一来实现这一点:要么在开始时执行该选项:

\pgfsetfillcolor{yellow}
\pgfpathcircle{\pgfpoint{-.5cm}{0cm}}{1cm}
...
\pgfusepath{fill}

或者在使用路径之前:

\pgfpathcircle{\pgfpoint{-.5cm}{0cm}}{1cm}
...
\pgfsetfillcolor{yellow}
\pgfusepath{fill}

第一个选择必须被拒绝,因为第一个例子将按如下方式实现

\pgfsetfillcolor{yellow}
\pgfpathcircle{\pgfpoint{-.5cm}{0cm}}{1cm}

% \pgfextra{\fill (.5,0) circle(1);}
\pgfpathcircle{\pgfpoint{.5cm}{0cm}}{1cm}
\pgfusepath{fill}

\pgfusepath{fill}

产生图片

                                                   该选项位于开始处。

但是第二种选择也必须被拒绝,因为第二个示例的实现将会看到在设置填充颜色之前创建的图形:

\pgfpathcircle{\pgfpoint{-.5cm}{0cm}}{1cm}

% graph{""[circle,at={(.5,0)},minimum size=2cm,fill]}

\pgfsetfillcolor{yellow}
\pgfusepath{fill}

并且从那时起图形在创建后被锁定在 TeX 框中,并且TeX 框的视觉外观在创建时就已确定,图形节点将填充黑色,图片看起来会像这样

                                                   填充选项在最后。

我们陷入了矛盾!

答案1

\pgfextra没有用pgfinterruptpath环境包围它的内容,因此:

\path[fill=yellow] (-.5,0) circle [radius=1]
  \pgfextra{\fill [red] (.5,0) circle [radius=1];};

“黄色”路径实际上是由 内部路径描述的路径延续的\pgfextra。然后“红色”路径使用该路径并填充红色。请注意,在 内部添加路径\pgfextra(无论是否带有pgfinterruptpath环境)是真的坏主意,行为本质上是未定义的。的预期用途\pgfextra主要是用于计算或一些简单的 TeX 内容,而不是任意的 TikZ 命令。此外,关于\pgfextra手册注意事项(第 162 页,我的重点):

...此操作仅应由真正的专家使用,并且只应在巧妙的宏内部深处使用,而不是在正常路径上使用

通过使用

\path[fill=yellow] (-.5,0) circle [radius=1]
  \pgfextra{
    \pgfinterruptpath
      \fill [red] (.5,0) circle [radius=1];
    \endpgfinterruptpath
  };

将绘制一个黄色和红色的圆圈。

答案2

TikZ“引擎”\path从左到右解析语句,并按文本顺序执行各部分。“执行”的含义取决于具体部分,例如

  • nodes 在保存到寄存器的 TeX 框内排版。在节点执行开始时,除 、 和 之外的所有路径选项font都会被暂时清除,textbehind path在节点执行结束时(排版后)恢复。因此,节点的排版完全基于显式应用于节点的选项(允许上述例外情况)。
  • 路径构造操作,例如circle(1)转换为\pgf...附加到内部列表对象的命令,称为当前路径(这是过于简单的说法,但就目前的目的而言,这样就够了)。重要的是要知道语句\path被包装在隐式 TeX 作用域中,但当前路径列表是全局的,因此它超越了作用域。
  • \pgfextra{<body>}暂时将控制权返回给普通的 TikZ 处理器(而不是路径解析器),将其<body>作为下一步要处理的文本传递。当前 TeX 范围不会退出,也不会打开新范围。遇到时引擎的内部状态\pgfextra不会改变,因此解析语句时创建的所有内部数据结构\path都保持不变。

    当 TikZ 完成处理后<body>,控制权将返回到路径解析器,其光标现在位于表达式的右侧\pgfextra{<body>}。同样,返回正常路径解析的转换不会伴随引擎状态的任何变化,也不会关闭任何范围,因为一开始就没有打开任何范围。

选项由命令执行\tikzset。这需要执行的内容取决于选项。例如

  • behind path选项对 TikZ 引擎的操作进行了内部更改:它使引擎将用此选项标记的节点排版到另一个框中,称为背景框,而不是其他节点排版的那个。

  • fill=<color>选项将命令附加\pgfsetfillcolor{<color>}到特殊列表,选项列表,由类似的基础层命令组成。此列表对 TikZ 引擎的操作没有影响。它只是累积并最终按原样写入 dvi 文件(在命令\pgf...被其\special实现替换之后)。与当前路径不同,选项列表是隐式包围命令的 TeX 范围的本地列表\path,并在此范围的开头设置为空列表。

因此,路径部分的执行通常意味着将其“翻译”为一系列低级命令,并将该序列保存为新对象,或将其附加到预先存在的列表中。

TikZ 路径末尾的分号的执行很特殊。它实际上需要将所有路径部分写入 dvi 文件(这是一个过于简单的说法,但对于我们的目的来说已经足够了)。由于在执行分号时,各个部分的执行已经导致它们被转换为低级命令,因此分号的主要作用是确定这些列表将被转储到 dvi 文件的顺序。

分号的简化执行版本是:

  1. 写出标有 的nodes(包括graph被视为美化的s) 。nodebehind path

  2. 写出选项列表。

  3. 写出当前路径。

  4. 使用路径,即绘制它、填充它等等。作为副作用,当前路径对象(回想一下,它是全局的)被重置。

  5. 写出其余的nodes(和graphs)。

  6. 关闭隐式包围路径语句的 TeX 范围,并恢复 TikZ 环境顶层的图形状态。特别是,在每个路径的末尾恢复 TikZ 环境的默认填充颜色,以及 TikZ 环境的许多其他默认值。


现在让我们将所有这些理论应用到原始帖子中的例子。

第一个例子

\path[fill=yellow] (-.5,0) circle(1) \pgfextra{\fill (.5,0) circle(1);};
  1. 执行该fill=yellow选项。这会导致指令\pgfsetfillcolor{yellow}被附加到最初为空的选项列表中。

  2. (-.5,0) circle(1)执行。这会导致指令\pgfpathcircle{\pgfpoint{-.5cm}{0pt}}{1cm}被添加到最初为空的当前路径。

  3. \pgfextra{\fill (.5,0) circle(1);}执行。这会导致 TikZ 暂时“忘记”它正在解析路径,并开始处理代码,\fill (.5,0) circle(1);就好像它是在顶层编写的一样。但是引擎的内部状态保持不变,因此,特别是选项列表和当前路径保持不变。

    1. (.5,0) circle(1)执行。这会导致指令\pgfpathcircle{\pgfpoint{.5cm}{0pt}}{1cm}被附加到当前路径对象,该对象已包含 (-.5,0) 处的圆。

    2. 遇到路径末尾的分号。这将引发以下一系列操作。

    3. 首先,behind path将节点写入 dvi 文件。没有节点,因此这是无操作。

    4. 其次,将选项列表写入 dvi 文件。这与上面提到的列表不同,因为选项列表是语句周围隐式作用域的本地列表\path,并且它们在此作用域的开头设置为空列表。由于未为 path 指定任何选项\fill (.5,0) circle(1);,因此选项列表为空,因此此步骤为无操作。

    5. 第三,将当前路径(由两个圆组成)的路径构造命令写入 dvi 文件。

    6. 第四,填充路径,即,\special将执行命令的 s\pgfusepath{fill}写入 dvi 文件。由于尚未应用顶级选项fill=yellow,因此使用默认颜色。在本例中,由于没有明确指定默认颜色(无论是 TikZ 图片还是文档),因此黑色是隐式默认颜色(因为这是所有 PDF 文档的默认颜色)。在此步骤结束时,将重置(全局)当前路径对象,即当前路径变为空。

    7. 第五,将其余的nodes 写入 dvi 文件。没有,所以这是无操作。

    8. 第六,隐式包围该语句的 TeX 作用域\fill ...被关闭,并且 TikZ 环境的默认设置被恢复。

  4. 执行顶层分号。但是,由于没有节点,并且当前路径为空,因此除了将选项列表写入 dvi 文件之外,没有其他事情可做。但是,这没有整体效果:选项列表将填充颜色更改为黄色,但是当分号执行的最后一步恢复 TikZ 环境的默认图形状态时,此更改将被推翻。

第二个例子

\path[fill=yellow] (-.5,0) circle(1)
    graph{""[circle,at={(.5,0)},minimum size=2cm,fill]};
  1. 与第一个例子一样。

  2. 与第一个例子一样。

  3. 图形被执行。图形的执行类似于节点的执行。因此,图形在保存到寄存器的 TeX 框内排版。当图形开始执行时,路径选项 ( fill=yellow) 被暂时清除,因此框内不会设置填充颜色。因此,当框的内容溢出到 dvi 文件时,填充颜色将用于渲染图形。

  4. 分号执行:

    1. behind path节点被写入 dvi 文件。没有节点,所以这是无操作。

    2. 选项列表,即\pgfsetcolor{yellow},写入 dvi 文件。

    3. 当前路径的路径构造命令(由以 (-.5,0) 为中心的圆组成)被写入 dvi 文件。

    4. 路径使用:将命令\pgfusepath{fill}写入 dvi 文件。当 dvi 文件将在屏幕上呈现时,此时会出现一个黄色圆圈。在此步骤结束时,(全局)当前路径对象将被重置,即当前路径变为空。

    5. 其余节点(和图形)都写入 dvi 文件。因此,现在图形框的内容已写入 dvi 文件。由于框内未设置填充颜色,因此将使用的填充颜色是最后设置的填充颜色,即黄色。

    6. 隐式包围该语句的 TeX 作用域\path ...被关闭,并且 TikZ 环境的默认设置被恢复。特别是,填充颜色被重置为默认的黑色。

第三个例子(Mark Wibrow 的例子)

\path[fill=yellow] (-.5,0) circle [radius=1]
  \pgfextra{
    \pgfinterruptpath
      \fill [red] (.5,0) circle [radius=1];
    \endpgfinterruptpath
  };
  1. 与第一个例子一样。

  2. 与第一个例子一样。

  3. 执行该\pgfextra块。这会导致 TikZ 暂时“忘记”它正在解析路径,并开始处理主体,\pgfextra就好像它是在顶层编写的一样。但是引擎的内部状态保持不变,因此,特别是选项列表和当前路径保持不变。

    1. \pgfinterrupt启动一个新的 TeX 块,并将当前路径(由以 (-.5,0) 为中心的圆组成)保存在本地“私有变量”中,并将(全局)当前路径重置为空路径。

    2. 选项red已执行。这实际上将指令\pgfsetfillcolor{red}和添加\pgfsetstrokecolor{red}到选项列表中。这与上面提到的列表不同,因为选项列表是围绕 \path 语句的隐式范围的本地列表,并且它们在此范围的开头设置为空列表。因此,在此步骤结束时,可见的选项列表仅包含两个命令,这些命令将填充和描边颜色设置为红色。

    3. (.5,0) circle [radius=1]执行。这会导致指令\pgfpathcircle{\pgfpoint{.5cm}{0pt}}{1cm}被附加到空的当前路径对象。

    4. 遇到路径末尾的分号。这会导致当前路径(由一个以 (.5,0) 为中心的圆圈组成)写入 dvi 文件,随后会发出指令用红色填充此圆圈。此后,当前路径将设置为空路径。当 dvi 文件在屏幕上呈现时,此处会出现一个红色圆圈。

    5. 包围命令的隐式 TeX 块\fill [red] (.5,0) circle [radius=1];已关闭。由于选项列表对于包围 \path 语句的隐式范围而言是本地的,因此选项列表将被前一个由单个命令组成的列表替换,该命令用于将填充颜色设置为黄色。

    6. \endpgfinterruptpath结束打开的 TeX 块\pgfinterruptpath,并将全局当前路径对象设置回该\pgfinterruptpath命令保存的对象,即由以 (-.5,0) 为中心的圆组成的对象。

  4. 顶级分号执行:

    1. behind path节点被写入 dvi 文件。没有节点,所以这是无操作。

    2. 选项列表,即\pgfsetcolor{yellow},写入 dvi 文件。

    3. 当前路径的路径构造命令(由以 (-.5,0) 为中心的圆组成)被写入 dvi 文件。

    4. 路径的使用:将命令\pgfusepath{fill}写入 dvi 文件。当 dvi 文件将在屏幕上呈现时,此时会出现一个黄色圆圈。它将绘制在之前绘制的红色圆圈的上方,略微偏左。在此步骤结束时,(全局)当前路径对象将被重置,即当前路径变为空。

    5. 其余节点(和图表)将写入 dvi 文件。没有,因此这是无操作。

    6. 隐式包围该语句的 TeX 作用域\path ...被关闭,并且 TikZ 环境的默认设置被恢复。特别是,填充颜色被重置为默认的黑色。

生成的图像是

                                                黄色圆圈下有一个红色圆圈

相关内容