在 TikZ 中,使用 \phantom 和 \pgfuseimage 在开始节点处执行?

在 TikZ 中,使用 \phantom 和 \pgfuseimage 在开始节点处执行?

问题

我想创建几个节点,其文本是图像的占位符,即\phantom{\pgfuseimage{mypicture}}。这很容易做到,例如:

\matrix {
  \node {\phantom{\pgfuseimage{firstpicture}}}; & \node {\phantom{\pgfuseimage{secondpicture}}}; \\
  \node {\phantom{\pgfuseimage{thirdpicture}}}; & \node {\phantom{\pgfuseimage{fourthpicture}}}; \\
  % etc.
};

问题是这太冗长了。我想避免必须输入\phantom{\pgfuseimage{...}}每个节点。除了节省击键次数外,它还会使代码更简洁、更易于阅读。

尝试的解决方案

定义宏来缩写

当然,我可以将命令定义\pgfimageplaceholder\phantom{\pgfuseimage{...}}。但我仍然需要\pgfimageplaceholder在每个节点中写入,因此宏并不能帮助节省许多击键次数或提高可读性。

使用在开始/结束节点处执行的键

在了解了如何matrix of nodes定义之后(第 207 页PGF/TikZ 2.10 手册),我想我可能能够使用TikZ 中的execute at begin nodeexecute at end node选项来分解\phantom{\pgfuseimage{...}}。按照的定义matrix of nodes,我尝试了:

\matrix [every node/.append style={execute at begin node=\phantom\bgroup\pgfuseimage\bgroup,
                                   execute at end node=\egroup\egroup}]
{
  \node {firstpicture}; & \node {secondpicture}; \\
  \node {thirdpicture}; & \node {fourthpicture}; \\
  % etc.
};

不幸的是,这会出现错误,Missing } inserted. 我从未做过任何纯 TeX 编码,所以我不熟悉和\bgroup。我使用它们正确吗?如果是这样,为什么会出现此错误?(有趣的是,如果我用和替换\egroup,则不会出现错误。)\phantom\bgroup\pgfuseimage\bgroup\textit\bgroup\egroup\egroup\egroup

使用 PGF 密钥

在回答这个问题的早期版本时,@AndrewStacey 建议使用密钥将图片名称传递给节点,而不是依赖节点文本。我以前从未创建过 PGF 密钥,但如果我理解正确的话,代码将类似于以下内容。(如果我误解了您的建议,请纠正我,@AndrewStacey。)

\pgfkeyssetvalue{/picture name}{}
\matrix [every node/.append style={execute at begin node=\phantom{\pgfuseimage{\pgfkeysvalueof{/picture name}}}}]
{
  \node[/picture name=firstpicture] {}; & \node[/picture name=secondpicture] {}; \\
  \node[/picture name=thirdpicture] {}; & \node[/picture name=fourthpicture] {}; \\
  % etc.
};

这是创建和使用密钥的正确方法吗?如果是这样,这种方法更易读,但不会节省太多击键次数。

使用 \foreach 构造

我考虑过使用\foreach,但我不知道如何将它与\matrix我想要的结构结合起来。

一个最小的工作示例

为了完整起见,这里有一个 MWE。

\documentclass{minimal}
\usepackage{tikz}
\usetikzlibrary{positioning}

\begin{document}

\pgfdeclareimage{firstpicture}{pic1.pdf}
\pgfdeclareimage{secondpicture}{pic2.pdf}
\pgfdeclareimage{thirdpicture}{pic3.pdf}
\pgfdeclareimage{fourthpicture}{pic4.pdf}
% etc.

\newcommand{\pgfimageplaceholder}[1]{\phantom{\pgfuseimage{#1}}}

\begin{tikzpicture}[every node/.style={draw=black},
                    every matrix/.style={draw=red}]

  % Here is a working example of what I need to do, 
  % but it is rather verbose.  I would rather not
  % have to type \phantom{\pgfuseimage{ each time.
  \matrix (part1)
  {
    \node {\phantom{\pgfuseimage{firstpicture}}}; & \node {\phantom{\pgfuseimage{secondpicture}}}; \\
    \node {\phantom{\pgfuseimage{thirdpicture}}}; & \node {\phantom{\pgfuseimage{fourthpicture}}}; \\
    % etc.
  };

  % I could replace \phantom{\pgfuseimage{ with a 
  % macro, but that doesn't save many keystrokes
  % or improve readability much.
  \matrix (part2)
          [right=of part1]
  {
    \node {\pgfimageplaceholder{firstpicture}}; & \node {\pgfimageplaceholder{secondpicture}}; \\
    \node {\pgfimageplaceholder{thirdpicture}}; & \node {\pgfimageplaceholder{fourthpicture}}; \\
    % etc.
  };

  % I was hoping to be able to somehow use the hooks 
  % execute at begin/end node to factor out the
  % repeated \phantom{\pgfuseimage{.
  \matrix (part3)
          [right=of part2
  % When the following two lines are uncommented,
  % the error is ``Missing } inserted.''
           % , every node/.append style={execute at begin node=\phantom\bgroup\pgfuseimage\bgroup,
           %                             execute at end node=\egroup\egroup}
          ]
  {
    \node {firstpicture}; & \node {secondpicture}; \\
    \node {thirdpicture}; & \node {fourthpicture}; \\
    % etc.
  };

  % Andrew Stacey suggested using a key to specify
  % the picture, rather than putting it in the node
  % text.  Here is my attempt at doing that.
  \pgfkeyssetvalue{/picture name}{}
  \matrix (part4)
          [right=of part3,
           every node/.append style={%
             execute at begin node=\phantom{\pgfuseimage{\pgfkeysvalueof{/picture name}}}}]
  {
    \node[/picture name=firstpicture] {}; & \node[/picture name=secondpicture] {}; \\
    \node[/picture name=thirdpicture] {}; & \node[/picture name=fourthpicture] {}; \\
    % etc.
  };
\end{tikzpicture}

\end{document}

答案1

评论中隐藏着很多信息,所以我将尝试将我的贡献提取成某种形式的答案。我的推荐的解决方案是使用pgfkeys。密钥长度可以很短 - 如果真的需要,可以是一个字母 - 因此使用它们所需的击键次数实际上并不比将图片名称放在节点文本中多多少。事实上,如果您要使用与某种模式匹配的名称(如示例中所示),则根本不需要指定图片名称,因此没有必要使用任何实际节点命令中的密钥。或者您可以修补unknown密钥处理程序,以便将未知密钥用作图片名称。pgfkeys我认为该方法是最可靠和最灵活的。例如,它可以与一起使用matrix of nodes

我列出的第二个解决方案是 Jake 的解决方案,即定义一个全新的宏。唯一的缺点是它不能透明地工作matrix of nodes(也就是说,您始终可以将新宏放入单元格中,但不能利用自动插入的节点)。

在我列表的最底下,有一个 hack 可以让你真正做到你最初要求的:使用节点文本作为宏的参数。这是一个非常糟糕的想法,原因有很多。首先,它涉及直接修改 TikZ 节点处理命令,而不是通过钩子。其次,节点文本的左括号实际上是在 TikZ 启动其节点机制之前被删除的(这是 TikZ 寻找的开始处理文本的信息,并且测试是破坏性的),所以我们必须再次将其放回原处,这意味着更多的 hackery。它是不是可以在开始节点/结束节点钩子中插入内容,原因有二:首先,这两个钩子之间出现的不仅仅是节点文本。它是:

\bgroup%
  \aftergroup\unskip%
  \ifx\tikz@textcolor\pgfutil@empty%
  \else%
   \pgfutil@colorlet{.}{\tikz@textcolor}%
  \fi%
  \pgfsetcolor{.}%
  \setbox\tikz@figbox=\box\pgfutil@voidb@x%
  \tikz@uninstallcommands%
  \aftergroup\tikz@fig@collectresetcolor%
  \tikz@halign@check%
  \ignorespaces%

后面跟着节点文本。节点文本之后和调用结束钩子之前还有更多垃圾。因此,如果我们想要获取实际的节点文本,我们需要在其后添加一些内容,\ignorespaces并且它必须有一个真正的左括号。

以下代码说明了上述所有解决方案。

\documentclass{article}

\usepackage{tikz}

\pgfdeclareimage{firstpicture}{vignettes.pdf}
\pgfdeclareimage{secondpicture}{vignettes.pdf}
\pgfdeclareimage{thirdpicture}{vignettes.pdf}
\pgfdeclareimage{fourthpicture}{vignettes.pdf}

\makeatletter
\newif\if@pgfimagefound
\tikzset{
  dummy image with key/.style={
    every node/.append style={%
      execute at end
node=\phantom{\pgfuseimage{\pgfkeysvalueof{/tikz/pname}}}
    },
  },
  pname/.initial={},
  dummy image/.style={
    every node/.append style={%
      execute at end
node=\phantom{\pgfuseimage{\pgfkeysvalueof{/tikz/pname}}},
    /tikz/.unknown/.add code=%
    {%
      \@ifundefined{pgf@image@\pgfkeyscurrentname!}{\@pgfimagefoundfalse}{%
        \let\tikz@key=\pgfkeyscurrentname
        \tikzset{pname/.expand once=\tikz@key}%
        \@pgfimagefoundtrue
      }%
      \if@pgfimagefound
      \else
    }%
    {
      \fi
    }
    }
  }
}

\let\orig@tikz@do@fig=\tikz@do@fig
\tikzset{
  slurp node text/.code={
    \def\tikz@do@fig{\orig@tikz@do@fig\expandafter\slurp@node\expandafter{\iffalse}\fi}
  },
  slurp node/.style={
    every node/.append style={
      slurp node text
    },
    slurp node command/.code={#1}
  },
}
\def\slurp@node#1{%
\pgfkeys{/tikz/slurp node command={#1}}%
\egroup}

\newcommand\imgnode[2][]{%
  \node[#1] {\phantom{\pgfuseimage{#2}}};}

\makeatletter
\begin{document}
\tikzset{every node/.append style={draw}}
\begin{tikzpicture}
\matrix [dummy image with key]
{
  \node[pname=firstpicture] {}; & \node[pname=secondpicture] {}; \\
  \node[pname=thirdpicture] {}; & \node[pname=fourthpicture] {}; \\
};
\end{tikzpicture}

\begin{tikzpicture}
\matrix [dummy image]
{
  \node[firstpicture] {}; & \node[secondpicture] {}; \\
  \node[thirdpicture] {}; & \node[fourthpicture] {}; \\
};
\end{tikzpicture}

\begin{tikzpicture}
\matrix
{
  \imgnode{firstpicture} & \imgnode{secondpicture} \\
  \imgnode{thirdpicture} & \imgnode{fourthpicture} \\
};
\end{tikzpicture}

\begin{tikzpicture}
\matrix [slurp node={\phantom{\pgfuseimage{#1}}}] {
  \node {firstpicture}; & \node {secondpicture}; \\
  \node {thirdpicture}; & \node {fourthpicture}; \\
};
\end{tikzpicture}
\end{document}

答案2

(评论太长了)

\phantom期望一个参数,即必须给予明确的括号或单个标记:所以

\phantom{A}

\def\x{A} \phantom\x

是等效的,因为参数将用于排版执行宏扩展的框。如果定义如上, TeX 很乐意执行\hbox{A}\hbox{\x},输出相同。\x

这一事实\textit\bgroup word\egroup可以用一个(非常)简化的版本来解释\textit

\def\textit#1{{\itshape #1}}

发生了什么\textit\bgroup word\egroup?就是这里。

  1. 的参数\textit\bgroup,因此标记列表变为

  2. {\itshape\bgroup} word\egroup

  3. \bgroup}互相抵消,而 while\egroup匹配第一个左括号。

(注:的实际定义\textit很多比这更复杂,但上述描述实际上是本质上发生的事情。

在某些情况下\bgroup\egroup are equivalent to{ and}`,但不是一般来说确实不是在抓住涉及参数。对于 TeX 基元,问题更加复杂:参见Taco Hoekwater 的回答

例如\hbox\bgroup word\egroup和 是一样的\hbox{word}。相反,\lowercase\bgroup WORD\egroup不是相当于\lowercase{WORD},而\lowercase\bgroup WORD}是 。:-)

答案3

也许你会喜欢这种可能性:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning,chains}
\begin{document}

\pgfdeclareimage{firstpicture}{pic1.pdf}
\pgfdeclareimage{secondpicture}{pic2.pdf}

\tikzset{
  phantom-content/.style={execute at begin node=\phantom{#1}},
  visible-content/.style={execute at begin node=#1},
  phantom-pgfimage/.style={phantom-content={\pgfuseimage{#1}}},
  visible-pgfimage/.style={visible-content={\pgfuseimage{#1}}},
  every node/.style={draw=red},
}

\begin{tikzpicture}
  \begin{scope}[start chain=going below]
    \node[phantom-content={\pgfuseimage{firstpicture}},on chain]{};
    \node[visible-content={\pgfuseimage{secondpicture}},on chain]{};
    \node[phantom-pgfimage={firstpicture},on chain]{};
    \node[visible-pgfimage={secondpicture},on chain]{};
  \end{scope}
\end{tikzpicture}

\foreach \vis in {phantom,visible}{
  \begin{tikzpicture}
    \tikzset{content/.style={\vis-content=##1}}
    \node[content={\pgfuseimage{firstpicture}}]{};
  \end{tikzpicture}
}

\foreach \vis in {phantom,visible}{
  \begin{tikzpicture}
    \tikzset{content/.style={\vis-pgfimage=##1}}
    \node[content={secondpicture}]{};
  \end{tikzpicture}
}
\end{document}

相关内容