# Scaling a glue in TeX

is there a possibility to scale a glue in TeX? Consider the following example:

\newskip\foo
\newskip\bar

\foo=10pt plus 2pt minus 3pt
\bar=2.5\foo\relax
\showthe\foo
\showthe\bar

\bye


When feeding this code into TeX, it will produce the following output in the log file, showing that the stretch and shrink of \foo are discarded during the assignment to \bar.

> 10.0pt plus 2.0pt minus 3.0pt.
l.6 \showthe\foo

?
> 25.0pt.
l.7 \showthe\bar

?


Apparently \foo is converted into a dimen by the scaling. Is there a way to get 25.0pt plus 5.0pt minus 7.5pt as a result? \multiply only works when the factor is an integer. At least a way to copy the stretch or shrink of a glue into a dimen register would suffice. But I didn't find an appropriate hint in chapters 12 and 15 of the TeXBook.

Does anybody know how to scale a glue correctly? Thanks in advance.

-
Are we allowed to use e-TeX? I suspect that the only way to do a non-integer multiplication here is to split the glue into parts and reassemble. –  Joseph Wright Sep 1 '14 at 14:23
@JosephWright Yes, or converting the factor to a fraction, and using \multiply and \divide. –  egreg Sep 1 '14 at 14:27
Not directly relevant to the question, but it would be nice to know what the aim is here :-) –  Joseph Wright Sep 1 '14 at 15:42
@JosephWright: I'm about to prepare a class for scientific posters and the Corporate Design of my University defines some scaling factors depending on the page size. My intention was to scale \abovedisplayskip, \belowdisplayskip, ... accordingly without reinventing the underlying beamer-class. I hope this isn't a bad idea. Because these skips usually doesn't contain fils, \gluestretch and \glueshrink were exactly what I was looking for. –  hamari Sep 1 '14 at 17:06

Multiplying by a decimal factor is allowed only for a <dimen>; when a <glue> is used, it is coerced to a <dimen>.

The syntax for a <dimen> includes

<factor><unit of measure>


and <unit of measure> can be <internal dimen> (any dimension register) or <internal glue> (any glue register); see the TeXbook, page 270.

On the other hand, the syntax for <glue> on the following page doesn't allow a <factor>.

One can use \skip0=2.5\skip2, but first 2.5\skip2 is coerced to a <dimen> and then the result is coerced to a <glue> which loses the stretchability and shrinkability components.

A way out could be using \multiply and \divide, that are allowed for glue, converting the factor to a fraction (but this can lead to overflow).

A simple set of macros for doing the scaling, assuming e-TeX can be used, is

\def\scaleglue#1#2#3{% #1 is a factor, #2 is a glue specification, #3 is a glue register
\scalegluetemp=#2\relax
#3=#1\scalegluetemp plus \scalegluestretch{#1} minus \scaleglueshrink{#1}%
\relax\relax
}
\newskip\scalegluetemp

\def\scalegluestretch#1{%
\strippt\dimexpr#1\gluestretch\scalegluetemp\relax
\ifcase\gluestretchorder\scalegluetemp pt \or fil \or fill \or filll \fi
}
\def\scaleglueshrink#1#2{%
\strippt\dimexpr#1\glueshrink\scalegluetemp\relax
\ifcase\glueshrinkorder\scalegluetemp pt \or fil \or fill \or filll \fi
}

\def\strippt{\expandafter\dostrippt\the}
\begingroup\catcodeP=12 \catcodeT=12
\lowercase{\endgroup\def\dostrippt#1PT{#1}}

\tt

\newskip\foo
\newskip\baz \baz=3pt minus -1filll

\scaleglue{1.2}{2pt plus 3pt}{\foo}\the\foo\par
\scaleglue{1.2}{2pt minus 3pt}{\foo}\the\foo\par
\scaleglue{1.2}{\baz}{\foo}\the\foo\par

\baz=0pt plus -1fil minus 3filll
\scaleglue{1.2}{\baz}{\foo}\the\foo\par

\bye


A simpler way can be with \glueexpr (compare with Heiko Oberdiek's answer)

\def\scaleglue#1#2#3{% #1 is a factor, #2 is a glue expression, #3 a glue parameter
#3=\glueexpr#2*\numexpr\dimexpr#1pt\relax\relax/65536\relax
}

\tt

\newskip\foo
\newskip\baz \baz=3pt minus -1filll

\scaleglue{1.2}{2pt plus 3pt}{\foo}\the\foo\par
\scaleglue{1.2}{2pt minus 3pt}{\foo}\the\foo\par
\scaleglue{1.2}{\baz}{\foo}\the\foo\par

\baz=0pt plus -1fil minus 3filll
\scaleglue{1.2}{\baz}{\foo}\the\foo\par

\bye


A different approach that doesn't use e-TeX, so it's much slower and complicated.

\catcode\@=11
\def\scaleglue#1#2#3{% #1 is a factor, #2 is a glue specification, #3 is a glue register
\def\sg@factor{#1}%
\sg@tempskip=#2\relax
\dimen@=\sg@tempskip
\edef\sg@tempa{\the\sg@tempskip}\edef\sg@tempb{\the\dimen@}%
\ifx\sg@tempa\sg@tempb
#3=#1\dimen@
\else
\sg@checkplusminus
\sg@bothfalse\ifsg@plus\ifsg@minus\sg@bothtrue\fi\fi
\ifsg@both
\expandafter\sg@getcompboth\the\sg@tempskip\relax
\else
\ifsg@plus
\edef\sg@minus{0\string p\string t}%
\expandafter\sg@getcompplus\the\sg@tempskip\relax
\else
\edef\sg@plus{0\string p\string t}%
\expandafter\sg@getcompminus\the\sg@tempskip\relax
\fi
\fi
\sg@makept\sg@plusunit\sg@plus
\sg@makept\sg@minusunit\sg@minus
\dimen\z@=\sg@plus pt \dimen\z@=#1\dimen\z@
\dimen\tw@=\sg@minus pt \dimen\tw@=#1\dimen\tw@
#3=#1\sg@tempskip
plus \expandafter\sg@strippt\the\dimen\z@ \sg@plusunit
minus \expandafter\sg@strippt\the\dimen\tw@ \sg@minusunit
\relax
\fi
}

\newskip\sg@tempskip
\newif\ifsg@minus
\edef\sg@plus{\string p\string l\string u\string s}
\edef\sg@minus{\string m\string i\string n\string u\string s}

\edef\sg@checkplusminus{%
\noexpand\expandafter\noexpand\sg@checkplus\noexpand\the\sg@tempskip\sg@plus\relax
\noexpand\expandafter\noexpand\sg@checkminus\noexpand\the\sg@tempskip\sg@minus\relax
}
\begingroup\edef\x{\endgroup\def\noexpand\sg@checkplus##1\sg@plus##2\relax}
\x{\if!#2!\sg@plusfalse\else\sg@plustrue\fi}
\begingroup\edef\x{\endgroup\def\noexpand\sg@checkminus##1\sg@minus##2\relax}
\x{\if!#2!\sg@minusfalse\else\sg@minustrue\fi}
\begingroup\edef\x{\endgroup\def\noexpand\sg@getcompboth##1\sg@plus##2\sg@minus##3\relax}
\x{\def\sg@plus{#2}\def\sg@minus{#3}}
\begingroup\edef\x{\endgroup\def\noexpand\sg@getcompplus##1\sg@plus##2\relax}
\x{\def\sg@plus{#2}}
\begingroup\edef\x{\endgroup\def\noexpand\sg@getcompminus##1\sg@minus##2\relax}
\x{\def\sg@minus{#2}}

\edef\sg@makept#1#2{%
\noexpand\expandafter
\noexpand\sg@makept@i
\noexpand\expandafter#1%
\noexpand\expandafter#2#2\string f\relax
}
\begingroup\edef\x{\endgroup\def\noexpand\sg@makept@i##1##2##3\string f##4\relax}
\x{%
\if!#4!%
\expandafter\sg@stripptx\expandafter#2#2\def#1{pt}%
\else
\def#2{#3}\sg@stripf#1#4%
\fi
}
\begingroup\edef\x{%
\endgroup\def\noexpand\sg@stripptx##1##2\string p\string t{\def##1{##2}}%
}\x
\begingroup\edef\x{%
\endgroup\def\noexpand\sg@strippt##1\string p\string t{##1}%
}\x
\begingroup\edef\x{%
\endgroup\def\noexpand\sg@stripf##1##2\string f{\def##1{f##2}}%
}\x
\newif\ifsg@plus
\newif\ifsg@minus
\newif\ifsg@both
\catcode\@=12 % end of macros

%%% testing
\tt

\newskip\foo
\newskip\baz \baz=3pt minus -1filll

\scaleglue{1.2}{2pt plus 3pt}{\foo}\the\foo\par
\scaleglue{1.2}{2pt minus 3pt}{\foo}\the\foo\par
\scaleglue{1.2}{\baz}{\foo}\the\foo\par

\baz=0pt plus -1fil minus 3filll
\scaleglue{1.2}{\baz}{\foo}\the\foo\par

\bye


Of course, rounding “errors” as usual. But for TeX, 1.2 times 3 is always 3.59999

-
A scaling operation with \glueexpr(...)*6/5\relax gives the correct 3.6 instead of 3.59999. –  Heiko Oberdiek Sep 1 '14 at 22:11
@HeikoOberdiek I'm sure it does. But the aim was not converting the factor to a fraction. AFAIK, e-TeX operations round rather than truncate. –  egreg Sep 1 '14 at 22:13

Multiplication and division by integers is possible. So you can do this:

\newskip\foo
\newskip\bar

\foo=10pt plus 2pt minus 3pt
\bar=\foo\relax
\multiply\bar by 10
\divide\bar by 4
\showthe\bar
\bye

-
Multiplication before division gives the better precision, however the intermediate result can exceed the allowed range (arithmetic overflow), even if the result is inside. If e-teX is available, I would use \glueexpr. –  Heiko Oberdiek Sep 1 '14 at 22:03

You can use \separateskip macro which separates the three parts of the skip register to the three dimens. The macro scans the \the output of the register. The usage is:

\separateskip\foo to \dimA \dimB \dimC


Now, the base part is stored in \dimA, the stretch part in \dimB and the shrink part in \dimC. You can do, for example:

\bar=2.5\dimA plus 4.7\dimB minus1.9\dimC


Of course, the fil(ll) units are not supported here because the parts are stored in the normal dimen registers. But you can do more elaborate macro which treats the fil(ll) units too, as an exercise.

\def\separateskip#1to#2#3#4{\def\tmpa{#3}\def\tmpb{#4}%
\expandafter\separateskipA\expandafter#2\the#1\end
}
\def\separateskipA#1{\afterassignment\separateskipB #1=}
\def\separateskipB#1\end{\if^#1^\tmpa=0pt \tmpb=0pt \let\next=\relax
\else \def\next{\separateskipC#1\end}%
\fi \next
}
\def\separateskipC#1{\if#1\string p\expandafter\separateskipP
\else \tmpa=0pt \expandafter\separateskipM\fi
}
\def\separateskipP#1 {\afterassignment\separateskipQ \tmpa=}
\def\separateskipQ#1\end{\if^#1^\tmpb=0pt \let\next=\relax
\else \def\next{\separateskipM#1\end}%
\fi \next
}
\def\separateskipM#1 {\afterassignment\separateskipN \tmpb=}
\def\separateskipN#1\end{}

\newskip\foo \newskip\bar
\newdimen\dimA  \newdimen\dimB  \newdimen\dimC

\foo=10pt plus3pt minus5pt

\separateskip\foo to \dimA \dimB \dimC

\bar=2.5\dimA plus 4.7\dimB minus1.9\dimC

\message{\the\foo, \the\bar}

\end

-

With e-TeX's glueexpr and converting the factor from a real number to an integer ratio:

\bar=\glueexpr\foo*5/2\relax


This is a "scaling operation, a multiplication followed by a division. In this case e-TeX uses 64-bit for the intermediate value and the result has to fit in the allowed range, of course.

-