TikZ 管道:路径的“绘制”选项对“路径后面”节点没有影响

TikZ 管道:路径的“绘制”选项对“路径后面”节点没有影响

仍然尝试通过分析源代码来找出 TikZ 管道(即 TikZ 路径在“执行”时转换为的一系列低级操作),具体来说<tex installation directory>/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex......

\tikz@handle宏被称为(在该宏前面的注释中)“命令的中央调度程序”。它是驱动 TikZ 图片“执行”的引擎。它的工作原理是识别输入流开头最小的语义上有意义的块,然后调用适当的处理程序。当处理程序完成处理时,它们会调用将\tikz@scan@next@command输入流“光标”移过刚刚处理的块,然后递归调用\tikz@handle

重要的是要认识到输入流从开始到结束都是线性扫描的,总是将“光标”向前移动,并且下一步要发生的动作是由当前“光标”处的块决定的。

\tikz@handle实现为一个长而简单的 switch 语句:

\def\tikz@handle{%
  \let\@next=\tikz@expand%
  \ifx\pgf@let@token(%)
    \let\@next=\tikz@movetoabs%
  \else%
    \ifx\pgf@let@token+%
      \let\@next=\tikz@movetorel%
    \else%
      \ifx\pgf@let@token-%
        \let\@next=\tikz@lineto%
      \else%
        \ifx\pgf@let@token.%
          \let\@next=\tikz@dot%
        \else%
          \ifx\pgf@let@token r%
            \let\@next=\tikz@rect%
          \else%
            \ifx\pgf@let@token n%
              \let\@next=\tikz@fig%
            \else%
              \ifx\pgf@let@token[%]
                \let\@next=\tikz@parse@options%
              \else%
              ...
            \fi%
          \fi%
        \fi%
      \fi%
    \fi%
  \fi%
  \@next%
}

从上面最后一个子句可以看出ifx,当遇到用左方括号标识的选项时,\tikz@parse@options将调用处理程序。这个简单的宏定义如下:

\def\tikz@parse@options#1]{%
  \tikzset{#1}%
  \tikz@scan@next@command%
}

它什么也不做,只是将选项传递给\tikzset,后者只是使用命令处理选项\pgfkeys,该命令在 TikZ & PGF 手册 3.0.1a 版第 82 节中有详细记录,默认路径设置为/tikz(见第 128 页)。

所有这些可以归结为以下结论。路径的“执行”形式

\path[<options>] ...;

以 开头<options><options>在路径上任何其他元素之前执行。

有了这种理解,让我们考虑以下包含简单 TikZ 图片的 LaTeX 手稿。图片由一条路径组成,该路径由原点处的单个空节点和绘制的边框组成。该路径只有一个选项:绘制颜色设置为red

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
    \tikz \path[draw=red] (0,0) node[draw] {};
\end{document}

根据上面强调的结论,我们预计该draw=red选项将在节点创建之前执行,因此节点边框的颜色应为红色。我们的预测是正确的:

一个节点

现在让我们将选项添加behind path到节点:

\tikz \path[draw=red] (0,0) node[draw,behind path] {};

这对我们的分析没有影响,因此我们期望得到与之前生成的图片相同的图片。可惜,我们的期望落空了:边框变成了黑色!

“路径后方”节点

为什么,为什么?添加的选项表示(手册第 215 页)节点应绘制在路径后面。在本例中,这是没有意义的,因为没有路径(或者更准确地说,路径是退化的,由原点处的单个点组成),但即使有路径,也无关紧要:节点的边框仍然会变黑,尽管已经确定路径选项(draw=red在本例中)首先执行。


附录

在对源代码进行一番挖掘和实验之后,我意识到这个故事比我上面所介绍的还要奇怪,而我应该问的真正问题不是:“为什么第二个例子中的节点边框是黑色的?”,而是:“为什么第一个例子中的节点边框是红色的?”

因为,你看,事实证明,在处理节点的初始阶段——每个节点- 清除选项,包括 stroke-color 选项(如果已设置),并且在渲染节点并最终锁定在 TeX 框中时,它们始终保持未设置状态。无论是否behind path指定了该选项,情况都是如此。

这意味着,即使在第一个例子中,即上面看到的具有红色边框的节点,当您在节点刚刚完成渲染并被包含在 TeX 框中时查看它时,它看起来与第二上面的图片,黑色的边界。

那么,一个被锁定在 TeX 框中的黑色边框节点(该框本身嵌套在另一个 TeX 框中)后来如何转变为红色边框节点呢?库施人能改变皮肤吗?豹子能改变斑点吗?

通过引用源代码来充实最后四段的主张:

  • 在节点处理的开始阶段,清除选项的位置在宏中\tikz@normal@fig

    \let\tikz@options=\pgfutil@empty
    

    您可以通过在行前\tikz@options添加\pgfsetstrokecolor{red}

    \wlog{options: \meaning\tikz@options}%
    
  • 节点的渲染和 TeX 框锁定在\tikz@fig@continue宏内部进行:

    \setbox\tikz@whichbox=\hbox{%
      \unhbox\tikz@whichbox%
      \hbox{{%
          \pgfinterruptpath%
            \pgfscope%
              \tikz@options%
              \setbox\tikz@figbox=\box\pgfutil@voidb@x%
              \setbox\tikz@figbox@bg=\box\pgfutil@voidb@x%
              \pgfmultipartnode{\tikz@shape}{\tikz@anchor}{\tikz@fig@name}{%
                \pgfutil@tempdima=\pgflinewidth%
                {\begingroup\tikz@finish}%
                \global\pgflinewidth=\pgfutil@tempdima%
              }%
            \endpgfscope
          \endpgfinterruptpath%
      }}%
    }%
    

    要查看节点在 TeX 框中渲染和锁定后的外观,请在上述代码后立即添加以下行:

    \shipout\expandafter\copy\tikz@whichbox%
    

    最终的 pdf 文件将会附加一个页面,显示此框的内容。

    顺便说一句,你应该验证\tikz@options上面的内容是否为空,即使behind path选项中有不是已给予!

  • 渲染节点的代码(如上一个要点所示)委托给\tikz@finish宏。此宏被调用两次:第一次是渲染节点,第二次是渲染整体路径。如果您在渲染节点之前\shipout单击节点的框 ( \tikz@whichbox),则在第二个示例中,会发生以下行:

    % Step 13: Add labels and nodes
    %
    \box\tikz@figbox%
    

    (当选项\tikz@whichbox\tikz@figboxbehind path不是指定),您会注意到节点的边界是黑色的!(请注意,您将在 pdf 文件的开头看到一个额外的空白页,因为节点正在渲染该框是空的;仅当节点被渲染后,该框才会出现,并包围该节点。)

    \wlog{here!}您可以通过附加到分支的末尾ifx并在以下命令之后的关键字\wlog{there!}后面来验证在渲染节点时未应用任何选项:\else

    %
    % Step 3: Setup options
    %
    \ifx\tikz@options\pgfutil@empty%
    \else%
      \pgfsys@beginscope%
        \let\pgfscope@stroke@color=\pgf@strokecolor@global%
        \let\pgfscope@fill@color=\pgf@fillcolor@global%
        \begingroup%
          \tikz@options%
    \fi%
    

    日志将包含两个条目:“这里!”和“那里!”,分别是节点和路径渲染的结果。

答案1

正如原帖所述,在节点处理的开始阶段,所有路径选项都会在本地关闭。这适用于所有节点,即使是那些不是标记为behind path。我所说的“本地”是指节点处理完成后,一旦 TikZ 恢复扫描路径的其余部分,选项就会恢复为原始值。有一些例外选项不受此影响,并保留其值,例如fonttext我认为这些是唯一的例外,但我不确定),但大多数选项(包括描边颜色和填充颜色)将在开始时关闭,并且除非明确设置为节点选项,否则将保持关闭状态,直到 TikZ 完成节点处理。

需要注意的是,behind path路径选项也是例外,虽然它在节点处理开始时在技术上被关闭,但与所有其他路径选项一样,它的效果不会被取消,因为关闭选项只是意味着没有\special为它们发出指令,但behind path并没有转换成\special指令,而是影响 TikZ 引擎本身的操作。

还应注意,在节点处理开始时关闭的路径选项只是在节点之前以文本形式定义的选项。在节点之后定义的路径选项不会被关闭,因为它们尚未设置,因为如原始帖子中所述,路径是线性扫描和“执行”的。对于大多数选项来说,这一点毫无意义,因为已关闭的选项和尚未设置的选项之间有什么区别!?但是,“例外”选项(fonttext)以及behind path根据它们在路径上相对于节点的位置会有不同的行为。例如,\path[text=blue] node{hello};将文本着色为蓝色,而\path node{hello} [text=blue];不会(除非blue事先已将默认字体颜色设置为)。

只有节点选项以及上面提到的例外路径选项(fonttext,如果已设置)才会转换为\special用于渲染节点的指令。如果 PDF 或 PostScript 查看器需要进一步的未指定信息(例如描边颜色(如果描边颜色未作为节点选项提供))才能在页面或屏幕上渲染节点,则将使用节点开始渲染之前的有效设置。因此,显式和隐式选项都可用于渲染节点。

当 TikZ 完成扫描描述节点的文本块时,节点将在 TeX 框内排版。这意味着\special渲染节点所需的 s 已生成并“锁定”在 TeX 框中。从现在开始,框的内容将保持不变:不会添加、删除或更改任何内容。此外,框的内容仅包含文字;它们不含变量、寄存器、宏和任何其他易于更改的 TeX 元素。最后,为节点的框赋予一个名称(或者更确切地说是一个数字;无论如何,它可以在以后引用)。

到目前为止,节点已在命名的 TeX 框内呈现,但尚未在页面上呈现。如果现在打印页面,则节点不会出现在页面上。

此时,TikZ 恢复扫描路径。当路径结束时(用分号识别),路径的所有部分(包括节点)都会呈现在页面上。标记为的节点behind path是第一个呈现的节点。它们的呈现方式是简单地将其框的内容附加到输出流。然后设置路径的选项,我的意思是将它们转换为\specials 并附加到输出流。然后对路径本身进行描边或填充。最后,其余节点通过将其框的内容附加到输出流来呈现。这不是完整的故事,但足以满足我们目前的目的。

当 PDF 或 PostScript 查看器打印或显示 TikZ 图片时,它会按照\specials写入 dvi 文件的顺序执行指定的指令。特别是,behind path节点将在执行路径选项之前渲染,因此任何缺失的信息都将从之前的图形状态推断出来。特别是,如果未将描边颜色指定为节点选项,它将从图形状态“继承”。在原始帖子中给出的第二个示例中,此颜色是文档的默认颜色,即黑色,但可以更改,例如通过在整个 TikZ 图片上设置选项(例如\tikz[color=blue] \path...)或在 TikZ 图片之前建立文档的默认颜色(\color{blue} \tikz \path ...)。

但是,其余节点(未标记的节点)behind path将在设置路径选项后进行渲染,因此,当显式节点选项不可用时,将隐式使用路径选项。这是第一个示例的情况。

相关内容