How to link 2 lengths

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


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

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

Tags:

Lengths