0

The following code compiles perfectly:

\documentclass[]{article}

\usepackage[most]{tcolorbox}

\newcommand\UseImageLeft[1]{% \IfFileExists{#1\i.jpg}% {\includegraphics[scale=0.2]{#1\i.jpg}}% {\includegraphics[scale=0.2]{example-image.jpg}}% }

\def\ImageLeft{\UseImageLeft{Image-}}

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

\begin{document}

\foreach \i in {1,...,2}{

% On any page (begin) \begin{tcolorbox}[] \begin{tcbitemize}[] \tcbitem[] \ImageLeft \end{tcbitemize} \end{tcolorbox} % On any page (end)

\newpage }

\end{document}

My problem is that I want to uncomment \pgfmathsetlengthmacro\LeftWidth{width("\ImageLeft")}, but then the code doesn't compile anymore. How can I fix this please?

O0123
  • 1,773
  • you are using a command \i in your command \UseImageLeft which has no meaning (or not the meaning you expect) outside the \foreach loop. – Ulrike Fischer Sep 06 '20 at 13:24
  • @UlrikeFischer What do you mean please? The loop is working fine for me? – O0123 Sep 06 '20 at 13:28
  • you have \IfFileExists{#1\i.jpg}%. And then you are using this outside the loop in \pgfmathsetlengthmacro. Which meaning has \i then in your opinion? – Ulrike Fischer Sep 06 '20 at 13:33
  • \i is to loop all of the pictures from 1 to 2 in this case (\foreach \i in {1,...,2}{) Please note all of the pictures are equally sized. I don't understand what exactly \pgfmathsetlengthmacro is or why I can't use it here. But I need it in the larger version of this document ... – O0123 Sep 06 '20 at 13:37
  • the question is not what you are hoping, but what you are doing, and in your example you are using \pgfmathsetlengthmacro and so \i outside the loop. – Ulrike Fischer Sep 06 '20 at 13:38
  • @UlrikeFischer I see. So It has to moved inside of the loop? – O0123 Sep 06 '20 at 13:41
  • What would be the purpose of executing \pgfmathsetlengthmacro outside of the loop? And what's the purpose of executing it in the loop, given that \LeftWidth is used nowhere we can see? – egreg Sep 06 '20 at 14:19
  • I am about to write another answer because using \i outside the \foreach-loop (the only place where \i is defined) is not the only problem. Another problem is \edef-evaluation pf pgfmath-expressions which takes place twice, implying two attempts at expanding \includegraphics within \edef-expansion-contexts while \includegraphics is not fully expandable but also yields non-expandable tokens like \def for defining temporary macros. – Ulrich Diez Sep 06 '20 at 14:36
  • The real actual code (not MWE) in which I want to apply this is the bottom code here: https://tex.stackexchange.com/a/561482/67761 Been trying to find a solution all day, but can't apply it ... – O0123 Sep 06 '20 at 14:44

2 Answers2

2

Don't use loop variables outside of a loop, neither in executed code, nor in definitions.

\documentclass[]{article}

\usepackage[most]{tcolorbox}

\newcommand\UseImageLeft[1]{% \IfFileExists{#1.jpg}% {\includegraphics[scale=0.2]{#1.jpg}}% {\includegraphics[scale=0.2]{example-image.jpg}}% }

\begin{document}

\foreach \i in {1,...,2}{% \pgfmathsetlengthmacro\LeftWidth{width("\UseImageLeft{image\i}")}% % On any page (begin) \begin{tcolorbox}[] \begin{tcbitemize}[] \tcbitem[] \UseImageLeft{image\i} \end{tcbitemize} \end{tcolorbox} % On any page (end)

\newpage }^

\end{document}

Ulrike Fischer
  • 327,261
  • @UlrikeFscher With your example on my system I still get ! Undefined control sequence. \in@ #1#2->\begingroup \def \in@@... This is because pgfmath-expressions are evaluated via \edef which implies evaluating \includegraphics via \edef which does not work out. You and I know, put probably not every other fellow reader knows, that \edef leaves \def alone while attempting to expand \in@@ which is undefined. (The sequence \def\in@@... shall define \in@@.) If you don't get this error, then probably I need to update my system. ;-) – Ulrich Diez Sep 06 '20 at 14:42
  • @UlrichDiez I have the habitude to test my examples, so yes it works for me. Is \includegraphics a protected macro for you? – Ulrike Fischer Sep 06 '20 at 14:46
  • It is not, but at the moment I have only a rather old TeX-distribution available via Knoppix on USB-stick - TeX Live 2019/dev/Debian, Package: graphicx 2017/06/01 v1.1a Enhanced LaTeX Graphics (DPC,SPQR). – Ulrich Diez Sep 06 '20 at 15:05
2

Problem 1:

You use \pgfmathsetlengthmacro\LeftWidth{width("\ImageLeft")} outside the \foreach-loop. But with \pgfmathsetlengthmacro\LeftWidth{width("\ImageLeft")} the macro \ImageLeft is used which in turn uses \UseImageLeft which in turn uses \i while \i is defined to yield the number of an image only within the \foreach-loop.

Problem 2:

You have \pgfmathsetlengthmacro\LeftWidth{width("\ImageLeft")} which is of pattern

\pgfmathsetlengthmacro\LeftWidth{⟨expression⟩}
with
⟨expression⟩ = width("\ImageLeft")
, while
width("\ImageLeft")
in turn is of pattern
width(⟨expression⟩)
with
⟨expression⟩ = "\ImageLeft".

This means that \ImageLeft is evaluated as part of an ⟨expression⟩ twice:

The first time when the ⟨expression⟩ forming the argument of \pgfmathsetlengthmacro\LeftWidth is evaluated.

The second time when the ⟨expression⟩ forming the argument of width(...) is evaluated.

pgfmanual.pdf, section "95.2 Syntax for Mathematical Expressions: Operators" says in the explanation of the "x"-operator:

... However, as every expression is expanded with \edef before it is parsed, macros (e.g., font commands like \tt or \Huge) may need to be “protected” from this expansion (e.g., \noexpand\Huge). Ideally, you should avoid such macros anyway. Obviously, these operators should be used with great care as further calculations are unlikely to be possible with the result.

This information is not related only to the "x"-operator. This information is relevant whenever it comes to evaluating an ⟨expression⟩. It is a general rule which is applied during the evaluation of every ⟨expression⟩. Therefore I think it is not a good idea to convey this information only while explaining a special operator.

Be that as it may:

Putting the pieces of information together, you find that \ImageLeft is evaluated via \edef twice. As the expansion-chain is \ImageLeft\UseImageLeft...\includegraphics..., this implies that two times attempts at evaluating \includegraphics via \edef take place.
But in case you don't use the one of the most recent TeX-distributions \includegraphics does not work out in \edef-expansion-contexts. This is because in older TeX-distributions \includegraphics is not "protected"—"protected" refers to a LaTeX-internal mechanism for preventing expansion of commands in situations where that expansion would not work out—and "relies on" performing temporary assignments, e.g., in terms of \def, while such assignments do not get carried out while \edef-evaluation is in progress. \edef triggers expansion of expandable tokens while \def is an unexpandable primitive.

So if not using one of the most recent TeX-distributions you need to take precautionary measures for preventing expanding/evaluating the command \ImageLeft (and thus also preventing expansion/evaluation of \includegraphics) with each of these \edef-evaluations. (On most recent TeX-distributions these precautionary measures neither are a nuisance nor are needed.)

You can prevent expansion/evaluation by prefixing \ImageLeft with \noexpand\noexpand\noexpand.

The first \edef-evaluation, applied on <code>\noexpand\noexpand\noexpand\ImageLeft</code>,
yields
<code>\noexpand\ImageLeft</code>.
The second \edef-evaluation will be applied on that, which in turn yields:
<code>\ImageLeft</code>.

Problem 3:

egreg pointed out that \i is already defined in LaTeX and that you probably don't want to override that definition. Therefore in the example below I used \NiceForEachElement instead of \i.

\documentclass[]{article}

\usepackage[most]{tcolorbox}

\newcommand\UseImageLeft[1]{% \IfFileExists{#1\NiceForEachElement.jpg}% {\includegraphics[scale=0.2]{#1\NiceForEachElement.jpg}}% {\includegraphics[scale=0.2]{example-image.jpg}}% }

\def\ImageLeft{\UseImageLeft{Image-}}

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

\begin{document}

\foreach \NiceForEachElement in {1,...,2}{% \pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft")}% %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 \end{tcbitemize}% \end{tcolorbox}% % On any page (end) \newpage }

\end{document}


If you wish to be able to use \ImageLeft outside the loop too, then define \ImageLeft etc to process an argument instead of processing \i/\NiceForEachElement.
Inside the loop you can pass \i/\NiceForEachElement as argument.
Outside the loop you can pass the number of the image as argument directly.

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

Ulrich Diez
  • 28,770
  • Thank you for this elaborate answer. Would the code need to be different then if it was \foreach \i in {1,...,10}{% instead of \foreach \i in {1,...,2}{%? – O0123 Sep 06 '20 at 14:34
  • Actually \i is defined outside of the loop. But its definition is not something that LaTeX is happy to see at that point. – egreg Sep 06 '20 at 14:39
  • @Ulrich Diez How would I make this code better then please? What would be a better solution to create the loop? I am hoping to apply this to the following full code: bottom code at https://tex.stackexchange.com/a/561482/67761 – O0123 Sep 06 '20 at 14:49
  • @VincentMiaEdieVerheyen No, the code would not need to be different. But egreg pointed out a circumstance which I overlooked: The command \i is already defined in the LaTeX-kernel. If you wish to use the command as defined by the LaTeX-kernel within the \foreach-loop, then your foreach-variable cannot have the name \i but should have some other name. Otherwise usage of \i as defined in the LaTeX-kernel will be overridden within the scoope of the \foreach-loop by usage of \i as foreach-variable. – Ulrich Diez Sep 06 '20 at 15:12
  • @UlrichDiez Your bottom code does not compile for me. It gives the error: \LeftWidth=macro:- and ->80.29857pt. and l.29 \show\LeftWidth – O0123 Sep 06 '20 at 15:50
  • @VincentMiaEdieVerheyen In the code provided by you, usage of the macro \i for denoting the number of the image is hardwired within the commands \ImageLeft/\UseImageLeft. Whenever you wish to use \ImageLeft/\UseImageLeft you are required to beforehand have \i (re)defined to deliver the desired image-number. If instead of this "hardwiring" you define \ImageLeft/\UseImageLeft to process another argument for denoting the image-number, then this redefining of \i beforehand is not needed. – Ulrich Diez Sep 06 '20 at 15:51
  • @UlrichDiez It sounds good in theory, I like it. But I can't compile the codes of your answer. I get errors. – O0123 Sep 06 '20 at 15:52
  • @VincentMiaEdieVerheyen So it does compile fine: \show\command causes TeX to display the current definition of \command on the consols/screen. The message you see is not an error-message but is the result of the \show-command and it just tells you that now \LeftWidth is defined as 80.29857pt. If you don't like this, just put a % in front of each of the two lines that contain \show. – Ulrich Diez Sep 06 '20 at 15:55
  • @VincentMiaEdieVerheyen I now commented out the \show-commands. Is everything okay now? – Ulrich Diez Sep 06 '20 at 16:00
  • @UlrichDiez Yes, everything works fine thank you. I will accept your answer. I am just struggling to apply it to the bottom code of https://tex.stackexchange.com/a/561482/67761 now, which is my goal all day: to loop that. I hope I can achieve that this weekend ... – O0123 Sep 06 '20 at 16:04
  • @VincentMiaEdieVerheyen I will have a look at it as soon as I have time to delve into that code. But I won't promise anything. ;-) – Ulrich Diez Sep 06 '20 at 16:52