8

Problem

I would like to create several nodes whose text is a placeholder for an image, i.e., \phantom{\pgfuseimage{mypicture}}. This is easily done as, for example:

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

The problem is that this is quite verbose. I would like to avoid having to type \phantom{\pgfuseimage{...}} in each node. Besides saving keystrokes, it would make the code much cleaner and easier to read.

Attempted solutions

Defining a macro to abbreviate

Of course, I could define a command \pgfimageplaceholder to be \phantom{\pgfuseimage{...}}. But I would still need to write \pgfimageplaceholder in each node, so the macro doesn't help save many keystrokes or improve readability much at all.

Using the execute at begin/end node keys

After seeing how matrix of nodes is defined (page 207 of the PGF/TikZ 2.10 manual), I thought that I might be able to use the execute at begin node and execute at end node options in TikZ to factor out \phantom{\pgfuseimage{...}}. Following the definition of matrix of nodes, I tried:

\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.
};

Unfortunately, this gives the error Missing } inserted. I've never done any plain TeX coding, so I'm not familiar with \bgroup and \egroup. Am I using them correctly? If so, why does this error occur? (Interestingly, I do not get an error if I replace \phantom\bgroup\pgfuseimage\bgroup with \textit\bgroup and \egroup\egroup with \egroup.)

Using a PGF key

In response to an earlier version of this question, @AndrewStacey suggested using a key to pass the picture name to the node, rather than relying on the node text. I've never created PGF keys before, but, if I understand them correctly, then the code would be something like the following. (Please correct me if I am misinterpreting your suggestion, @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.
};

Is this the correct way to create and use a key? If so, this approach is more readable, but doesn't save very many keystrokes.

Using a \foreach construction

I thought about using \foreach, but I do not see how to combine it with the \matrix structure that I want.

A minimal working example

For completeness, here is a 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}
David Carlisle
  • 757,742
  • @Jake But, since I want to have a different picture within each node, the font option doesn't quite do what I want. – Henry DeYoung Jun 13 '12 at 20:48
  • \node[execute at begin node=\begingroup\textit, execute at end node=\endgroup] {mypicture}; – Alain Matthes Jun 13 '12 at 20:48
  • @HenryDeYoung: Ah yes, sorry, I misunderstood the question. – Jake Jun 13 '12 at 20:49
  • 2
    The analysis Jake mentioned in a now-deleted comment was at http://tex.stackexchange.com/a/48968/86 Why does the picture name have to be in the node text? It could be in a key given to the node whereupon the code executed at the start or end of the node could get the picture name from that key. – Andrew Stacey Jun 13 '12 at 20:53
  • Excuse my ignorance but how come \textit plays a role here? Also with the same ignorance this looks like an XY problem. Can you give a concrete example about the task at hand? Maybe we can look at it together and find a way that doesn't involve a lot of customization. – percusse Jun 13 '12 at 22:15
  • The error messages you get from the commented out code is not particularly related to tikz, you can not use \begingroup in that way. \textit\begingroup... is \textit{\begingroup}... so the argument to \textit is an unmatched \begingroup and things fall over. Same with any macro such as \pgfuseimage or \phantom. – David Carlisle Jun 13 '12 at 22:24
  • Looking back at my analysis, one thing to note is that the begin and end hooks are not executed right before and after the node text is read, so the result of putting \pgfuseimage\bgroup and \egroup is not \pgfuseimage\bgroup{node text}\egroup but actually a lot more goes in between. So I think your best bet is to put the whole code in one of these hooks and use a pgfkey to set the image name. That would actually be quite straightforward, would it be an acceptable solution? – Andrew Stacey Jun 14 '12 at 08:56
  • @AndrewStacey @percusse @Altermundus I've made substantial edits to my question. Hopefully these clarify what I'm trying to do and also avoid the XY problem. The suggestion of @Altermundus works for \textit but not for \phantom{\pgfuseimage{...}}. – Henry DeYoung Jun 14 '12 at 20:27
  • 1
    I'm not sure if I understand the question correctly, but couldn't you just define a command \newcommand\imgnode[1]{\node {\phantom{\pgfuseimage{#1}}}? Then then you could type \imgnode{firstpicture};, which saves plenty of keystrokes but is still reasonably readable. – Jake Jun 14 '12 at 20:46
  • @Jake I guess that the one thing I didn't include in my question is that I will need to label these nodes and perhaps pass options to them. So the \imgnode command would be slightly more complicated than what you wrote, but not much more complicated. So far, IMO yours is the best solution--I don't know why I didn't think of that! For its own sake, I'm still interested in understanding what goes wrong with execute at begin node=\phantom\bgroup\pgfuseimage\bgroup, however. – Henry DeYoung Jun 14 '12 at 20:59
  • 1
    @HenryDeYoung What goes wrong with the begin node/end node version is as I explained just above: there is more between these hooks than just the node text and the whole lot gets slurped in as the argument to the command. For \textit that doesn't matter but for \pgfuseimage then it does as it wants just a file name. Your attempt with pgfkeys is pretty much what I was thinking. To save key strokes, you could make the key name considerably shorter. But Jake's suggestion of a wrapper macro is probably best: \newcommand\imgnode[2][]{\node[#1] {\phantom{\pgfuseimage{#2}};}. – Andrew Stacey Jun 14 '12 at 21:08
  • @AndrewStacey Ok, I think that I understand now. In a sense, the argument passed to \pgfuseimage is treated verbatim, but this is not the case for \textit. So, in principle, if one could redefine \pgfuseimage to not treat its argument verbatim, then the hook-based approach might work. Is that right? Of course, redefining \pgfuseimage would be much, much more complicated than just using Jake's suggestion. – Henry DeYoung Jun 14 '12 at 21:20

3 Answers3

5

(Just too long for a comment)

\phantom expects an argument, that must be given in explicit braces or as a single token: so

\phantom{A}

and

\def\x{A} \phantom\x

are equivalent, because the argument will be used to typeset a box where macro expansion is performed. And TeX is happy to do \hbox{A} and \hbox{\x}, with the same output if \x is defined as above.

The fact that \textit\bgroup word\egroup works can be explained with a (very) simplified version of \textit:

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

What happens with \textit\bgroup word\egroup? Here it is.

  1. The argument to \textit is \bgroup so the token list becomes

  2. {\itshape\bgroup} word\egroup and

  3. \bgroup and } cancel each other, while \egroup matches the first open brace.

(Note: the actual definition of \textit is much more complicated than this, but the description above is really what essentially happens.)

In some cases \bgroup and \egroup are equivalent to{and}`, but not in general and definitely not when grabbing of macro arguments is involved. With TeX primitives the question is quite more intricated: see this answer by Taco Hoekwater

For example \hbox\bgroup word\egroup is just the same as \hbox{word}. Instead, \lowercase\bgroup WORD\egroup is not equivalent to \lowercase{WORD}, while \lowercase\bgroup WORD} is. :-)

egreg
  • 1,121,712
  • We should probably link this to questions like http://tex.stackexchange.com/q/2976/86 for more detail on the difference between \bgroup ... \egroup and { ... }. – Andrew Stacey Jun 15 '12 at 06:45
  • @egreg Thanks for the explanation! Now I understand why it works for \textit but not for \phantom and \pgfuseimage. – Henry DeYoung Jun 16 '12 at 15:28
5

There's a lot of information buried in the comments so I'm going to try to extract my contributions into some form of answer. My recommended solution is to use pgfkeys. The key lengths can be short - single letter if really wanted - so the number of key strokes needed to use them is not really that much more than putting the picture name in the node text. Indeed, if you are going to use names that match some pattern (as in the example), there's no need to specify the picture name at all whereupon it isn't necessary to use any key in the actual node commands. Or you could patch into the unknown key handler so that an unknown key is taken to the the name of a picture. The pgfkeys method is the most robust, I think, and the most flexible. For example, it will work with matrix of nodes.

Second in my list of solutions would be Jake's solution of defining an entirely new macro. The only downside to this is that it doesn't work transparently with matrix of nodes (that is, you can always put the new macro into the cells but you can't take advantage of the automatically inserted nodes).

Way, way down at the bottom of my list is a hack that allows you to actually do what you originally asked: use the node text as the argument to a macro. This is a Very Bad Idea for lots of reasons. Firstly, it involves modifying the TikZ node processing commands directly rather than via hooks. Secondly, the opening bracket of the node text is actually removed before TikZ starts its node machinery (this is what TikZ looks for to know to start processing the text and the test is destructive) so we have to put it back again and this means More Hackery. It is not possible to insert stuff in the begin node/end node hooks for two reasons: Firstly, what appears between those two hooks is not just the node text. It is:

\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%

followed by the node text. There's even more junk after the node text and before the end hook is called. So if we want to get at the actual node text we need to add something after that \ignorespaces and it has to have a genuine opening brace.

Here's code illustrating all the above-mentioned solutions.

\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}
David Carlisle
  • 757,742
Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • Using .unknown keyhandler seems dangerous. The risk is a clash between names of pictures and some valid TikZ keys. – Paul Gaborit Jun 15 '12 at 09:01
  • @PolGab Yes, you have to ensure that your picture names are not valid TikZ keys so ultra thick.png might have to be renamed! But the original questioner places considerable weight on the conciseness of the code so I wanted to show that there were ways to make the pgfkeys method really incredibly short. – Andrew Stacey Jun 15 '12 at 09:05
  • @AndrewStacey Thanks for the detailed answer! Now I understand your earlier comment much better. I decided to go with your first suggestion. When used in combination with matrix of nodes, it is especially slick: \matrix [matrix of image placeholders] { |[img=firstpicture]| & |[img=secondpicture]| \ |[img=thirdpicture]| & |[img=fourthpicture]| \}; One last question: Are there any subtle differences between your version and @PolGab's, or are the differences purely stylistic? – Henry DeYoung Jun 16 '12 at 15:32
3

Maybe you will like this possibity:

\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}
Paul Gaborit
  • 70,770
  • 10
  • 176
  • 283