TransWikia.com

Why can I not newcommand inside a tikzpicture of this macro?

TeX - LaTeX Asked on January 27, 2021

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.
newcommandNiceForEachElement{}%

newcommandUseImageLeft[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 ===================================
%pgfmathsetlengthmacroLeftWidth{width("ImageLeft")}
% =======================================

begin{document}

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

pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of LeftWidth looks like now:
%showLeftWidth

% inside the loop:

foreach NiceForEachElement in {1,...,2}{%
  pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of LeftWidth looks like now:
  %showLeftWidth
  % 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.
newcommandNiceForEachElement{}%

newcommandUseImageLeft[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 ===================================
%pgfmathsetlengthmacroLeftWidth{width("ImageLeft")}
% =======================================

begin{document}

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

pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of LeftWidth looks like now:
%showLeftWidth

% inside the loop:

foreach NiceForEachElement in {1,...,2}{%
  pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of LeftWidth looks like now:
  %showLeftWidth
  % 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.
newcommandNiceForEachElement{}%

newcommandUseImageLeft[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 ===================================
%pgfmathsetlengthmacroLeftWidth{width("ImageLeft")}
% =======================================

begin{document}

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

pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of LeftWidth looks like now:
%showLeftWidth

% inside the loop:

foreach NiceForEachElement in {1,...,2}{%
  pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of LeftWidth looks like now:
  %showLeftWidth
  % 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 ???


newcommandxRuler[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.

One Answer

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 defpgffor@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

expandafternewcommandcsnameforeachvariable CommonNamePartendcsname[1]{...}
globalexpandafterletcsnameforeachvariable CommonNamePartexpandafterendcsnamecsnameforeachvariable CommonNamePartendcsname

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.
newcommandNiceForEachElement{}%

newcommandUseImageLeft[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:

pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of LeftWidth looks like now:
%showLeftWidth

% inside the loop:

foreach NiceForEachElement in {1,...,2}{%
  pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of LeftWidth looks like now:
  %showLeftWidth
  % 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
  %
  newcommandxRuler[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.
newcommandNiceForEachElement{}%

newcommandUseImageLeft[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}}

newcommandxRuler[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:

pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of LeftWidth looks like now:
%showLeftWidth

% inside the loop:

foreach NiceForEachElement in {1,...,2}{%
  pgfmathsetlengthmacroLeftWidth{width("noexpandnoexpandnoexpandImageLeft{NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of LeftWidth looks like now:
  %showLeftWidth
  % 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}

Correct answer by Ulrich Diez on January 27, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP