0

Consider the answer of Ulrich Diez of this post: \pgfmathsetlengthmacro uncompatible with a loop?:

\documentclass[]{article}

\usepackage[most]{tcolorbox}

% Define the command \NiceForEachElement to ensure error-message in case it is already defined.
% This way you can ensure to a certain degree that using `\niceelement` as Foreach-variable
% does not override something that alerady exists.
\newcommand\NiceForEachElement{}%

\newcommand\UseImageLeft[2]{%
% #1 preceding phrase "image-"
% #2 number of image
\IfFileExists{#1#2.jpg}%
  {\includegraphics[scale=0.2]{#1#2.jpg}}%
    {\includegraphics[scale=0.2]{example-image.jpg}}%
}

\newcommand*\ImageLeft[1]{\UseImageLeft{Image-}{#1}}

% Problem ===================================
%\pgfmathsetlengthmacro\LeftWidth{width("\ImageLeft")}
% =======================================

\begin{document}

% outside the loop the width of Image-7.jpg or example-image.jpg:

\pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of \LeftWidth looks like now:
%\show\LeftWidth

% inside the loop:

\foreach \NiceForEachElement in {1,...,2}{%
  \pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{\NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of \LeftWidth looks like now:
  %\show\LeftWidth
  % On any page (begin)
  \begin{tcolorbox}[]%
  \begin{tcbitemize}[]%
  \tcbitem[] \ImageLeft{\NiceForEachElement}%
  \end{tcbitemize}%
  \end{tcolorbox}%
  % On any page (end)
  \newpage
}

\end{document}

It compiles great.

It also works fine with tikz. See this working MWE:

\documentclass[]{article}

\usepackage{tikz}% 💖💖💖 ADDED THIS 💖💖💖

\usepackage[most]{tcolorbox}

% Define the command \NiceForEachElement to ensure error-message in case it is already defined.
% This way you can ensure to a certain degree that using `\niceelement` as Foreach-variable
% does not override something that alerady exists.
\newcommand\NiceForEachElement{}%

\newcommand\UseImageLeft[2]{%
% #1 preceding phrase "image-"
% #2 number of image
\IfFileExists{#1#2.jpg}%
  {\includegraphics[scale=0.2]{#1#2.jpg}}%
    {\includegraphics[scale=0.2]{example-image.jpg}}%
}

\newcommand*\ImageLeft[1]{\UseImageLeft{Image-}{#1}}

% Problem ===================================
%\pgfmathsetlengthmacro\LeftWidth{width("\ImageLeft")}
% =======================================

\begin{document}

% outside the loop the width of Image-7.jpg or example-image.jpg:

\pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of \LeftWidth looks like now:
%\show\LeftWidth

% inside the loop:

\foreach \NiceForEachElement in {1,...,2}{%
  \pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{\NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of \LeftWidth looks like now:
  %\show\LeftWidth
  % On any page (begin)
  \begin{tcolorbox}[]%
  \begin{tcbitemize}[]%
  \tcbitem[] \ImageLeft{\NiceForEachElement}%
  \end{tcbitemize}%
  \end{tcolorbox}%
  % On any page (end)
    
\begin{tikzpicture}% 💖💖💖 ADDED THIS 💖💖💖
\filldraw[black] (0,0) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};% 💖💖💖 ADDED THIS 💖💖💖
\end{tikzpicture}% 💖💖💖 ADDED THIS 💖💖💖
  
  \newpage
}

\end{document}

However, a problem seems to arise as soon as I want to make a \newcommand inside the tikzpicture:

\documentclass[]{article}

\usepackage{tikz}% 💖💖💖 ADDED THIS 💖💖💖

\usepackage[most]{tcolorbox}

% Define the command \NiceForEachElement to ensure error-message in case it is already defined.
% This way you can ensure to a certain degree that using `\niceelement` as Foreach-variable
% does not override something that alerady exists.
\newcommand\NiceForEachElement{}%

\newcommand\UseImageLeft[2]{%
% #1 preceding phrase "image-"
% #2 number of image
\IfFileExists{#1#2.jpg}%
  {\includegraphics[scale=0.2]{#1#2.jpg}}%
    {\includegraphics[scale=0.2]{example-image.jpg}}%
}

\newcommand*\ImageLeft[1]{\UseImageLeft{Image-}{#1}}

% Problem ===================================
%\pgfmathsetlengthmacro\LeftWidth{width("\ImageLeft")}
% =======================================

\begin{document}

% outside the loop the width of Image-7.jpg or example-image.jpg:

\pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of \LeftWidth looks like now:
%\show\LeftWidth

% inside the loop:

\foreach \NiceForEachElement in {1,...,2}{%
  \pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{\NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of \LeftWidth looks like now:
  %\show\LeftWidth
  % On any page (begin)
  \begin{tcolorbox}[]%
  \begin{tcbitemize}[]%
  \tcbitem[] \ImageLeft{\NiceForEachElement}%
  \end{tcbitemize}%
  \end{tcolorbox}%
  % On any page (end)
    
\begin{tikzpicture}% 💖💖💖 ADDED THIS 💖💖💖
\filldraw[black] (0,0) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};% 💖💖💖 ADDED THIS 💖💖💖


\newcommand\xRuler[2]{ 🍀🍀🍀🍀 NOW ADDED THIS 🍀🍀🍀🍀
\filldraw[black] (#1,#2) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture}; 🍀🍀🍀🍀 NOW ADDED THIS 🍀🍀🍀🍀
} 🍀🍀🍀🍀 NOW ADDED THIS 🍀🍀🍀🍀

\end{tikzpicture}% 💖💖💖 ADDED THIS 💖💖💖
  
  \newpage
}

\end{document}

Why is this not compiling? How can I make this compile without errors please?


P.S.: This is a minimal problem of Code 3 (trying to build/debug) of an answer to another OP.

1 Answer 1

1

You are almost there:

\newcommand doesn't matter within a \foreach-loop because every iteration of the \foreach-loop takes place within its own group/local scope. After an iteration the \newcommand performed within that iteration is gone with the local scope within which the iteration took place.

But you need to double hashes that denote arguments of macros defined within a \foreach-loop.

The reason is:

The basic pattern of \foreach is:

\foreach⟨comma-list or macro expanding to comma-list⟩{%
  ⟨Tokens to carry out repeatedly⟩%
}

Beneath other actions \foreach triggers defining a temporary macro \pgffor@body from ⟨Tokens to carry out repeatedly⟩.

Something like \def\pgffor@body{⟨Tokens to carry out repeatedly⟩} .

\pgffor@body does not have a parameter-text/does not process arguments. But if ⟨Tokens to carry out repeatedly⟩ contains things like #1 or #2, then LaTeX assumes that these are intended to denote arguments of \pgffor@body and raises an error-message:

 Illegal parameter number in definition of \pgffor@body.
<to be read again> 
                   1

So when ⟨Tokens to carry out repeatedly⟩ itself contains macro definitions, then with these macro definitions hashes need to doubled—hashes always need to be doubled with the inner macro definitions when it comes to nesting macro definitions within other macro definitions.

I don't know if the need of doubling hashes of macro-definitions inside \foreach is documented in pgfmanual.pdf. If not, it might be worth dropping a hint to the author/maintainer of tikz/pgf.

But I think this is rarely needed as usually you can do with defining macros outside the loop.

But sometimes I use the \foreach-variables as parts of names of macros which are to be defined within \foreach in terms of

\expandafter\newcommand\csname\foreachvariable CommonNamePart\endcsname[1]{...}
\global\expandafter\let\csname\foreachvariable CommonNamePart\expandafter\endcsname\csname\foreachvariable CommonNamePart\endcsname

in order to have them available outside the \foreach-loop also.

Here the need of doubling hashes is relevant.

When encountering error-messages of the kind Illegal parameter number in definition of..., then it is always a good idea to check if nesting of macro definitions within macro definitions occurs and if hash-doubling with inner macro definitions was done properly.

\documentclass[]{article}

\usepackage{tikz}%

\usepackage[most]{tcolorbox}

% Define the command \NiceForEachElement to ensure error-message in case it is already defined.
% This way you can ensure to a certain degree that using `\niceelement` as Foreach-variable
% does not override something that alerady exists.
\newcommand\NiceForEachElement{}%

\newcommand\UseImageLeft[2]{%
% #1 preceding phrase "image-"
% #2 number of image
\IfFileExists{#1#2.jpg}%
  {\includegraphics[scale=0.2]{#1#2.jpg}}%
    {\includegraphics[scale=0.2]{example-image.jpg}}%
}

\newcommand*\ImageLeft[1]{\UseImageLeft{Image-}{#1}}

\begin{document}

% outside the loop the width of Image-7.jpg or example-image.jpg:

\pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of \LeftWidth looks like now:
%\show\LeftWidth

% inside the loop:

\foreach \NiceForEachElement in {1,...,2}{%
  \pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{\NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of \LeftWidth looks like now:
  %\show\LeftWidth
  % On any page (begin)
  \begin{tcolorbox}[]%
  \begin{tcbitemize}[]%
  \tcbitem[] \ImageLeft{\NiceForEachElement}%
  \end{tcbitemize}%
  \end{tcolorbox}%
  % On any page (end)

  \begin{tikzpicture}%
  \filldraw[black] (0,0) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};%
  %
  % DOUBLE THE HASHES IN THE DEFINITION OF \xRuler
  % DOUBLE THE HASHES IN THE DEFINITION OF \xRuler
  %
  \newcommand\xRuler[2]{%
   \filldraw[black] (##1,##2) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};
  }%
  \end{tikzpicture}%
  \newpage
}

\end{document}

Be aware:

As your definition of \xRuler does not depend on the expansion of the \foreach-variable-macros, defining \xRuler can take place outside \foreach, this will save LaTeX a lot of repeated defining-work:

\documentclass[]{article}

\usepackage{tikz}%

\usepackage[most]{tcolorbox}

% Define the command \NiceForEachElement to ensure error-message in case it is already defined.
% This way you can ensure to a certain degree that using `\niceelement` as Foreach-variable
% does not override something that alerady exists.
\newcommand\NiceForEachElement{}%

\newcommand\UseImageLeft[2]{%
% #1 preceding phrase "image-"
% #2 number of image
\IfFileExists{#1#2.jpg}%
  {\includegraphics[scale=0.2]{#1#2.jpg}}%
    {\includegraphics[scale=0.2]{example-image.jpg}}%
}

\newcommand*\ImageLeft[1]{\UseImageLeft{Image-}{#1}}

\newcommand\xRuler[2]{%
 \filldraw[black] (#1,#2) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};
}%

\begin{document}

% outside the loop the width of Image-7.jpg or example-image.jpg:

\pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of \LeftWidth looks like now:
%\show\LeftWidth

% inside the loop:

\foreach \NiceForEachElement in {1,...,2}{%
  \pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{\NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of \LeftWidth looks like now:
  %\show\LeftWidth
  % On any page (begin)
  \begin{tcolorbox}[]%
  \begin{tcbitemize}[]%
  \tcbitem[] \ImageLeft{\NiceForEachElement}%
  \end{tcbitemize}%
  \end{tcolorbox}%
  % On any page (end)

  \begin{tikzpicture}%
  \filldraw[black] (0,0) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};%
  \end{tikzpicture}%
  \newpage
}

\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .