3

I am trying to write a macro with variable number of inputs (this is important to me) that should return the maximum of the lengths of the different inputs. It uses the foreach macro from pgffor:

\documentclass{article}

\usepackage{pgffor} %\usepackage{pgfpages}

\newcommand{\getmaxlength}[1]{% \newlength{\Lmax} \newlength{\Lcurr} \setlength{\Lmax}{0pt} \foreach \arg in {#1} {% Previous max: \the\Lmax\par \settowidth{\Lcurr}{\arg} \ifdim \Lcurr>\Lmax \global\setlength{\Lmax}{\Lcurr} \fi \arg\ is of length \the\Lcurr\par New max: \the\Lmax\par } }

\begin{document} \getmaxlength{A,AAA,AA} \end{document}

Which works as expected on this toy example:

    Previous max: 0.0pt
    A is of length 7.50002pt
    New max: 7.50002pt
    Previous max: 7.50002pt
    AAA is of length 22.50005pt
    New max: 22.50005pt
    Previous max: 22.50005pt
    AA is of length 15.00003pt
    New max: 22.50005pt

However, this solution is neither clean, nor robust:

  • It requires to make a global assignment at line 13. I found this by trial-and-error, but I assume it's not clean, and I do not know the side effects.
  • The macro breaks down if I uncomment the \usepackage(pgfpages) on line 4, another hint that it probably does something wrong.

In either cases (removing the global keyword, using package pgfpages), I obtain a behavior where Lmax is reset at every repetition of the loop:

    Previous max: 0.0pt
    A is of length 7.50002pt
    New max: 7.50002pt
    Previous max: 0.0pt
    AAA is of length 22.50005pt
    New max: 22.50005pt
    Previous max: 0.0pt
    AA is of length 15.00003pt
    New max: 15.00003pt

I figure this has to do with the way foreach handles local vs. global variables, and that a potential solution resides in using its remember option (see here). However I could not get this to work. (Also remember that Lmax is not a `classic' variable, but a length variable).

So, what is the cleanest way of making my bit of code robust to these issues?

(BTW, the use case is the following: I am trying to build a 'local overprint' command for beamer equations (e.g. change x(x+1) to x^2+x inside the equation) that will respect a fixed width for all the overlays, in order to be more visible.)

HowieR
  • 31
  • \global\setlength{\Lmax}{\Lcurr} is absolutely not guaranteed to work and certainly will not work if the calc package is loaded. (\setlength is a macro not a primitive so the \global will do nothing if the first token in its expansion is not an assignment) – David Carlisle Jan 04 '21 at 11:46
  • unrelated but your \newlength should be outside the macro otherwise you use up two registers on every use, and you are missing % from ends of lines, the current version will add multiple space tokens which may affect the poutput, depending where it is used. – David Carlisle Jan 04 '21 at 12:03
  • @DavidCarlisle : thanks. If I understand correctly your explanation, \global\someMacro{...} is generally not robust and will work only by accident, whereas \global\someTexPrimitive{...} is ok. What I still don't understand is how the variable scoping works here. If \Lmax is defined outside the foreach loop, why is it reset at every iteration? – HowieR Jan 05 '21 at 08:54
  • The pgf for loop by design choice executes each iteration in a local group, so you either need to make the assignment escape that group or use a different loop macro that does not put a group around each iteration. – David Carlisle Jan 05 '21 at 08:59
  • Thanks! And if I choose to stick to pgffor, the only way of making the assignment "espace the local group" is to use a global variable? – HowieR Jan 05 '21 at 09:06
  • it is sometimes possible to lift the value exactly one level but I don't advise it it relies on knowing all the implementation details of the loop macro knowing exactly how many groups it adds and exactly where they are relative to the loop infrastructure see https://tex.stackexchange.com/q/470961/1090 (just noticed that one references me:-) – David Carlisle Jan 05 '21 at 09:11
  • thanks for the link, very instructive indeed! – HowieR Jan 05 '21 at 12:52

1 Answers1

2

The reasons for which \global\setlength fails with pdfpages are

  1. this package loads calc;
  2. if calc is not loaded, \global\setlength works by accident.

I suggest a different approach which avoids global settings. The macro \getmaxlength has an optional argument which should be something that wraps the resulting dimension, by default just showing the value.

\documentclass{article}

\ExplSyntaxOn

%% define a command with %% 1. optional argument #1 and default value \dim_eval:n %% 2. mandatory argument #2 (which should be a comma separated list %% The command just transfer the action to the internal function \NewDocumentCommand{\getmaxlength}{ O{\dim_eval:n} m } { \howier_getmaxlength:nn { #1 } { #2 } }

%% allocate variables of type dimension and box \dim_new:N \l_howier_getmaxlength_max_dim \box_new:N \l__howier_getmaxlength_temp_box

%% the main function \cs_new_protected:Nn \howier_getmaxlength:nn { % set the value to zero to start \dim_zero:N \l_howier_getmaxlength_max_dim % map over the list given as second argument, using one item at a time \clist_map_function:nN { #2 } __howier_getmaxlength_do:n % use the final value (by default #1 would be \dim_eval:n) #1 { \l_howier_getmaxlength_max_dim } }

%% the auxiliary function taking as argument each item in the list \cs_new_protected:Nn __howier_getmaxlength_do:n { % store the current list item in a box so we can access to its width \hbox_set:Nn \l__howier_getmaxlength_temp_box { #1 } % set the dimension variable to the maximum between the previous % value and the width of the current item \dim_set:Nn \l_howier_getmaxlength_max_dim { \dim_max:nn { \l_howier_getmaxlength_max_dim } { \box_wd:N \l__howier_getmaxlength_temp_box } } }

\ExplSyntaxOff

\begin{document}

\getmaxlength{A,AAA,AA}

XAAAX

X\getmaxlength[\hspace]{A,AAA,AA}X

AAAX

\getmaxlength[\hspace*]{A,AAA,AA}X

\end{document}

enter image description here

Note: if you're running LaTeX prior to release 2020-10-01, you need

\usepackage{xparse}
egreg
  • 1,121,712
  • Thanks, this looks great. The only caveat for me is, I have absolutely no idea how it works, and that's a bit frustrating! ;) I assume this mostly relies on the functionalities of xparse? Could you be maybe explain very briefly the global working principle here? – HowieR Jan 05 '21 at 09:00
  • @HowieR I commented the code – egreg Jan 05 '21 at 09:10
  • That's really clear now, thanks a lot! – HowieR Jan 05 '21 at 09:12
  • What I seem to understand is that \clist_map_function:nN is the loop macro, and unlike PGF's foreach macro, it does not open a local group, so we can update the value of the length storing the maximum. Is that correct? – HowieR Jan 05 '21 at 09:17
  • @HowieR That's correct: \clist_map_function:nN loops through the list given as first argument and passes each item to the function given as second argument. – egreg Jan 05 '21 at 09:33
  • Ok, I just realized that macro \clist_map_function:nN, as well as your naming syntax, are those of LaTeX3. While this is probably obvious to many, I'll leave it as a comment for ignorants like me :) – HowieR Jan 05 '21 at 14:03