7

I would really like to create a length \bar that defaults to having the same as the length \foo even if \foo gets changed, but I would also like the user to be able to set \bar to be whatever length they want. I can get close if instead of a length \bar I use a macro \baz, but the user would need to be careful with the usage. What is the best way to go about this?

enter image description here

\documentclass{article}
\let\bar\relax
\newlength{\foo}
\newlength{\bar}\setlength{\bar}{\foo}
\newlength{\qux}
\def\baz{\foo}

\begin{document}
\setlength{\foo}{100pt}\bigskip
foo: \the\foo\par bar: \the\bar\par baz: \the\baz\par

\setlength{\foo}{2\baz}\bigskip
foo: \the\foo\par bar: \the\bar\par baz: \the\baz\par

\def\baz{400pt}
\setlength{\qux}{2\baz}\bigskip
% Note that \baz no long works with \the
foo: \the\foo\par bar: \the\bar\par baz: \baz\par
% Note that \qux is "wrong"
qux: \the\qux\par
\end{document}
  • For one thing, since \baz is a string, not a length, use \setlength{\qux}{2\dimexpr\baz} – Steven B. Segletes Dec 29 '16 at 18:14
  • 1
    This is something similar to what xassoccnt does for counters, I think – user31729 Dec 29 '16 at 18:19
  • Related maybe? tex.stackexchange.com/questions/123443/… – Steven B. Segletes Dec 29 '16 at 18:25
  • You could also define \def\baz{\dimexpr\foo} if you always wanted \baz to behave like a length, but change as \foo changes. This is unlike \bar, which is fixed with the \setlength, regardless of how \foo subsequently changes. – Steven B. Segletes Dec 29 '16 at 18:31
  • @StevenB.Segletes the problem with \dimexpr is it loses any strecthable glue. – StrongBad Dec 29 '16 at 19:22
4

This is a similar approach that I used in my xassoccnt package for counters: associating a length to a 'master' length, say '\foo'.

If \foo is assigned a new value with \setlength, all lengths associated with it will get the same value.

I defined \addtolength etc, as well as removal and synchronization. The starred version of \setlength and \addtolength will manipulate the master length only!

However, a direct manipulation of \foo will not work for \baz etc.

\documentclass{article}


\usepackage{xparse}

\makeatletter
\let\latex@setlength\setlength
\let\latex@addtolength\addtolength

\newcommand{\stripslash}[1]{%
  \expandafter\@gobble\string#1
}

\ExplSyntaxOn

\cs_new:Nn \strongbad_associate_lengths:nn {%
  \seq_set_from_clist:cn {g_strongbad_#1_lengths_seq} {#2} % Populate unexpanded
  \seq_remove_duplicates:c {g_strongbad_#1_lengths_seq}
  \seq_remove_all:cn {g_strongbad_#1_lengths_seq} {#1}% Prevent self - association!
  \seq_map_inline:cn {g_strongbad_#1_lengths_seq} {
    \dim_if_exist:NF { ##1 } {% Preventing complaining about already existing length variables
      \newlength{##1}
    }
  }
}


\NewDocumentCommand{\RemoveAssociatedLengths}{mm}{%
  \seq_if_exist:cT { g_strongbad_ \stripslash{#1} _lengths_seq } {
    \seq_set_from_clist:Nn \l_tmpa_seq {#2}
    \seq_map_inline:Nn \l_tmpa_seq {
      \seq_remove_all:cn { g_strongbad_ \stripslash{#1} _lengths_seq } {##1}
    }
  }
}

\NewDocumentCommand{\DeclareAssociatedLength}{mm}{%
  \strongbad_add_associated_length:nn {#1}{#2}
}

\cs_new:Nn \strongbad_add_associated_length:nn {%
  \seq_if_exist:cF { g_strongbad_\stripslash{#1}_lengths_seq} {
    \seq_new:c {g_strongbad_\stripslash{#1}_lengths_seq}
  }
  \strongbad_associate_lengths:nn{\stripslash{#1}}{#2}
}


\NewDocumentCommand{\AddAssociatedLengths}{mm}{%
  \strongbad_associate_lengths:nn{\stripslash{#1}}{#2}  
}

\RenewDocumentCommand{\addtolength}{smm}{%
  \IfBooleanF{#1}{% No starred command
    \seq_if_exist:cT { g_strongbad_ \stripslash{#2} _lengths_seq } {
      \seq_map_inline:cn {  g_strongbad_ \stripslash{#2} _lengths_seq } {
        \latex@addtolength{##1}{#3}
      }% End of \seq_map_inline
    }% End of \seq_if_exist
  }% End of \IfBooleanF
  \latex@addtolength{#2}{#3}
}

\NewDocumentCommand{\synclengths}{m}{%
  \seq_if_exist:cT { g_strongbad_ \stripslash{#1} _lengths_seq } {
    \dim_set:Nn \l_tmpa_dim {\the#1}
    \setlength{#1}{\l_tmpa_dim}
  }
}

\NewDocumentCommand{\syncaddtolength}{mm}{%
  \addtolength*{#1}{#2}%
  \synclengths{#1}%
}


\RenewDocumentCommand{\setlength}{smm}{%
  \IfBooleanF{#1}{% No starred command
    \seq_if_exist:cT { g_strongbad_ \stripslash{#2} _lengths_seq } {
      \seq_map_inline:cn {  g_strongbad_ \stripslash{#2} _lengths_seq } {
        \latex@setlength{##1}{#3}
      }% End of \seq_map_inline
    }% End of \seq_if_exist
  }% End of \IfBooleanF
  \latex@setlength{#2}{#3}
}

\makeatother


\ExplSyntaxOff

\begin{document}

\newlength{\foo}

\newlength{\boz}

\DeclareAssociatedLength{\foo}{\baz,\buz,\boz,\biz}

\setlength{\foo}{100pt}


foo: \the\foo

baz: \the\baz

buz: \the\buz

boz: \the\boz

biz: \the\biz

\setlength{\baz}{\dimexpr200pt+\foo}

Foo again: \the\foo

Baz now: \the\baz

buz: \the\buz

boz: \the\boz

biz: \the\biz

Now using the starred version:

\setlength*{\foo}{5000pt}

Foo after starred version: \the\foo

Baz after starred version: \the\baz


Adding some value:

\addtolength{\foo}{100pt}

Foo after adding some value: \the\foo

Baz after adding some value to foo: \the\baz


Adding some value with synchronization first:

\syncaddtolength{\foo}{100pt}

Foo after adding some value: \the\foo

Baz after adding some value to foo: \the\baz

Buz after adding some value to foo: \the\buz

Removing buz and biz:

\RemoveAssociatedLengths{\foo}{\buz,\biz}

\addtolength{\foo}{-1000pt}

New foo: \the\foo

New biz: \the\biz

New buz: \the\buz



\end{document}

enter image description here

  • There is \cs_to_str:N. ;-) – egreg Dec 29 '16 at 18:57
  • @egreg: Really? Well, I reinvented the wheel then... – user31729 Dec 29 '16 at 18:57
3

\baz expands to \foo, which is a length, and therefore 2\baz works in the second length setting. However, letting \def\baz{400pt} doesn't make \baz a length anymore. Yes, technically it expands to a valid length, but it can't be used as-is in a length product. You'll have to explicitly use \dimexpr, otherwise the values are concatenated rather than multiplied:

enter image description here

\documentclass{article}

\let\bar\relax% Just for this example

\newlength{\foo}% \foo is a length
\newlength{\bar}\setlength{\bar}{\foo}% \bar is a length
\newlength{\qux}% \qux is a length
\def\baz{\foo}% \baz expands to \foo, which is a length

\begin{document}

\setlength{\foo}{100pt}\bigskip
foo: \the\foo\par bar: \the\bar\par baz: \the\baz\par

\setlength{\foo}{2\baz}\bigskip
foo: \the\foo\par bar: \the\bar\par baz: \the\baz\par

\def\baz{400pt}% \baz is no longer a length
\setlength{\qux}{2\dimexpr\baz}\bigskip
foo: \the\foo\par bar: \the\bar\par baz: \the\dimexpr\baz\par
qux: \the\qux\par

\end{document}

On the somewhat sly side, you could

\def\baz{\dimexpr400pt}
3

You can create linked lengths, but you need different commands (or to override the standard ones). The syntax is similar to \newcounter.

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\NewLength}{mo}
 {
  \newlength{#1}
  \seq_new:c { g_strongbad_length_ \cs_to_str:N #1 _seq }
  \IfValueT{#2}
   {
    \seq_gput_right:cn { g_strongbad_length_ \cs_to_str:N #2 _seq } { #1 }
   }
 }
\NewDocumentCommand{\SetLength}{mm}
 {
  \setlength{#1}{#2}
  \seq_map_inline:cn { g_strongbad_length_ \cs_to_str:N #1 _seq }
   {
    \setlength{##1}{#2}
   }
 }
\ExplSyntaxOff

\NewLength{\foo}
\NewLength{\baz}[\foo]
\NewLength{\qux}

\begin{document}

\SetLength{\foo}{100pt}\bigskip
foo: \the\foo\par baz: \the\baz\par

\SetLength{\foo}{2\baz}\bigskip
foo: \the\foo\par baz: \the\baz\par

\SetLength{\baz}{10pt}\bigskip
foo: \the\foo\par baz: \the\baz\par

\SetLength\baz{400pt}
\SetLength{\qux}{2\baz}\bigskip
qux: \the\qux\par

\end{document}

Every defined length has an associated list of lengths, which are set to the same value as the master length as soon as this is modified (with \SetLength). I leave to you as an exercise to implement \AddToLength.

enter image description here

  • Ah.... I was a little bit faster than you, having the same idea ;-) – user31729 Dec 29 '16 at 18:59
  • Any chance that \SetLength\baz{400pt} could "break" the association such that a future \SetLength{\foo}{100pt} would not change \baz? – StrongBad Dec 29 '16 at 19:40
  • @StrongBad That's a very different thing. It can be done, but I can't see what's the use of it. By the way, the assignments respect groups. – egreg Dec 29 '16 at 20:29
  • I have an image/logo that should default to \textwidth (maybe \linewidth), but if the user really wants it to be 6cm, then I don't want changes to \textwidth to interfere. – StrongBad Dec 29 '16 at 20:34
  • @StrongBad \textwidth shouldn't change in the document. This seems to be an XY-question. – egreg Dec 29 '16 at 20:41
3

Let's say I want a length that is double \foo minus 3 pt. Using a length to define it (as in the case of \barr), it is fixed at the time of definition, so that subsequent changes in \foo have no influence on \barr.

However, using a \def that begins with \dimexpr (as in the case of \baz) allows \foo and \baz to be eternally linked into the x2 - 3pt relationship, even as \foo changes. Furthermore, \baz acts like a length, even though it is not, so that \the\dimexpr3\baz yields the expected length result.

\documentclass{article}

\newlength\foo
\newlength\barr
\setlength\foo{100pt}
\setlength\barr{\dimexpr2\foo-3pt}
\def\baz{\dimexpr2\foo-3pt\relax}
\begin{document}
\the\foo, \the\barr, \the\baz

\setlength\foo{200pt}
\the\foo, \the\barr, \the\baz

\the\dimexpr3\baz

\end{document}

enter image description here

FOLLOW UP

The question arose on how this \dimexpr approach can handle glue. The answer is yes it can, if \relax is used to terminate the \dimexpr before the glue is added. Thus,

\documentclass{article}

\newlength\foo
\setlength\foo{100pt}
\def\baz{\dimexpr2\foo-3pt\relax minus20pt}
\begin{document}

\foo=100pt\relax

x\hspace{\baz}x

x\hspace{197pt minus 20pt}x

x\hspace{197pt}x

\noindent\hrulefill

\foo=165pt\relax

x\hspace{\baz}x

x\hspace{327pt minus 20pt}x

x\hspace{327pt}x

\end{document}

enter image description here

As can be seen, the glue is operational in the expansion of \baz.

  • I am not sure, but I think \glueexpr might be better than \dimexpr: tex.stackexchange.com/questions/245635/… – StrongBad Dec 29 '16 at 20:54
  • @StrongBad Thanks for the link. I really had no familiarity with \glueexpr. Whether it makes sense for your application depends on whether you want the glue to scale with multiplication or not. So indeed, one could use \def\baz{\glueexpr\dimexpr2\foo-3pt\relax minus 20pt\relax} – Steven B. Segletes Dec 29 '16 at 20:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.