How to scale multiple elements to maximum width while keeping their relative size fixed?

I have multiple elements in different places of my document. These could be images, TikZ drawings, etc. I want to set the maximum width for each of the elements. Importantly, I also want the elements to be scaled by the same factor, so that their relative sizes are preserved. This factor should be the largest possible such that none of the elements exceeds the width specified for it.

I know that I can scale an element to a certain width using \resizebox{!}{<width>}{<element>}. However, this scales each element independently by its own scaling factor. Instead, I would like to scale all elements within a group by the same scaling factor, automatically computed as the minimum over the individual scaling factors. The result should look like this:

\begin{figure}[t]
\centering
\resizetogether{mygroup1}{0.3\textwidth}{
\includegraphics{example-image-a}
}
\caption{Image}
\end{figure}
# ...
\resizetogether{mygroup1}{0.2\textwidth}{
\includegraphics{example-image-b}
}
# ...
\resizetogether{mygroup1}{12em}{
\begin{tikzpicture}
\draw[orange, ultra thick] (4,0) -- (6,0) -- (5.7,2) -- cycle;
\end{tikzpicture}
}


This imaginary command \resizetogether{<group>}{<width>}{<element>} would have to:

1. Measure the width of what is passed as <element>.
2. Compute the preliminary scaling factor as the specified <width> divided by the width of the element.
3. Wait until the end of the document for other \resizetogether calls with the same <group>.
4. Scale its element by the smallest of all preliminary factors in the same group.
5. Insert the scaled element into the document.

How can this be done in LaTeX?

• Find a scaling factor and then use \scalebox{<factor>}{<stuff>} with the same scaling factor for each. – Werner Nov 5 '18 at 17:00
• @Werner Thanks. The problem is that the scaling factor changes every time I update the images. That's why I'm looking for a solution that automatically finds the scaling factor. – danijar Nov 5 '18 at 18:03
• One can figure out the scaling factor using some calculations. However, it's difficult to guess how to do that from your description. It would be really helpful if you could post a minimal example the highlights what you're after. – Werner Nov 5 '18 at 18:05
• @Werner Good idea. I've updated my question to provide an example. As you said, the solution I'm looking for needs to calculate the scaling factors for each sub figure and then use the smallest for all of them. The problem is mainly how to exchange information between the multiple boxes that are used within the same figure. – danijar Nov 5 '18 at 21:34

The following minimal example provides \setscalefactor{<len>}{<csv list of objects>} that defines a macro \scalefactor to contain the smallest scaling factor necessary to fit each object in a width of <len>. List processing is made capable using etoolbox's \docsvlist and \do. Expandable length/floating point calculations are done using xfp.

As an extra, \includegraphics is updated locally to update the scale key-value to default to \scalefactor after \setscalefactor. This way you can just include the images as-is within specifying scale = \scalefactor explicitly.

\documentclass{article}

\usepackage{graphicx,subcaption,xfp,etoolbox,letltxmacro}

\makeatletter
\newcommand{\setscalefactor}[2]{%
\def\scalefactor{9999}% Default (large) scale factor
\renewcommand*{\do}[1]{% How each element will be processed
\settowidth{\@tempdima}{##1}% Store width of element
\ifdim\scalefactor pt > \fpeval{(#1) / \@tempdima} pt
\xdef\scalefactor{\fpeval{(#1) / \@tempdima}}% Update scaling factor
\fi
}%
\docsvlist{#2}% Process list of elements
% Change the default scale factor for \includegraphics
\LetLtxMacro\oldincludegraphics\includegraphics
\renewcommand{\includegraphics}[2][]{%
\oldincludegraphics[scale = \scalefactor, ##1]{##2}%
}
}
\makeatother

\begin{document}

\begin{figure}

% Identify the scale factor to fit content within 0.4\linewidth
\setscalefactor{0.4\linewidth}{%
\includegraphics{example-image}, % First image
\includegraphics{example-image-a4-landscape} % Second image
}

\subcaptionbox{}[0.4\linewidth]{%
\includegraphics{example-image}%
}\hfill%
\subcaptionbox{}[0.4\linewidth]{%
\includegraphics{example-image-a4-landscape}%
}%
\caption{A figure caption}
\end{figure}

\end{document}


Subfloats are set using subcaption's \subcaptionbox.

For a different setup and syntax using

\resizetogether{<group>}{<width>}{<stuff>}


the following code suffices:

\documentclass{article}

\usepackage{graphicx,subcaption,xfp,tikz}

\makeatletter

\newcommand{\resizetogether}[3]{%
\settowidth{\@tempdimc}{#3}% Store the width of #3
\immediate\write\@auxout{%
\string\setminscalefactor{#1}{\fpeval{(#2) / (\@tempdimc)}}%
}%
\ifcsname #1@minscalefactor\endcsname
\else
\expandafter\def\csname #1@minscalefactor\endcsname{#2}%
\fi
\expandafter\scalebox\expandafter{\csname #1@minscalefactor\endcsname}{#3}%
}

\newcommand{\setminscalefactor}[2]{%
\ifcsname #1@minscalefactor\endcsname
\expandafter\expandafter\expandafter\ifdim\csname #1@minscalefactor\endcsname pt>#2 pt
\expandafter\gdef\csname #1@minscalefactor\endcsname{#2}%
\fi
\else
\expandafter\gdef\csname #1@minscalefactor\endcsname{#2}%
\fi
}

\makeatother

\begin{document}

\begin{figure}

\subcaptionbox{}[0.3\linewidth]{%
\resizetogether{mygroup1}{\linewidth}
{\includegraphics{example-image}}%
}\hfill%
\subcaptionbox{}[0.3\linewidth]{%
\resizetogether{mygroup1}{\linewidth}
{\includegraphics{example-image-a4-landscape}}%
}\hfill
\subcaptionbox{}[0.3\linewidth]{%
\resizetogether{mygroup1}{\linewidth}
{\begin{tikzpicture}
\draw[orange, ultra thick] (4,0) -- (6,0) -- (5.7,2) -- cycle;
\end{tikzpicture}}%
}
\caption{A figure caption}
\end{figure}

\end{document}


The .aux file associated with the above minimal example looks like this:

\relax
\setminscalefactor{mygroup1}{0.3222332023696596}
\setminscalefactor{mygroup1}{0.1224799241153155}
\setminscalefactor{mygroup1}{1.769082649490795}
\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces A figure caption\relax }}{1}}


The idea follows the principles of eqparbox where the scaling factor for each element is written to the .aux. When the .aux is read in \AtBeginDocument (by default), \setminscalefactor{<group>}{<factor>} identifies the minimum scaling for all elements within the same <group>, defining a macro called \<group>@minscalefactor. If this macro exists, it's used to scale <stuff>.

eqparbox provides a warning when content changes. My code doesn't, so you'll have to compile at least twice with any change in <stuff> for any <group>.

• Thanks, this is great! Is there a way to specify the images only once? I'd like to use this with TikZ drawings and repeating these or moving them all into separate files is cumbersome. – danijar Nov 6 '18 at 21:07
• @danijar: Try with this code. It defines \setandscaleimages{<len>}{<csv of objects>} that does \setscalefactor and then sequentially sets the images. For a single image, I assume you'd use \centering (you can incorporate in the macro as well). – Werner Nov 7 '18 at 1:40
• Thanks. this goes in the right direction. I'm still trying to generalize this to also work with inline TikZ drawings. Your code already shows how to get the width of a macro argument. Now the question is if it's possible to have a macro defer it's output until all other macros of the same group have run, so that the scaling factor is synchronized. That would allow for the syntax suggested in my question. Is that possible in LaTeX? – danijar Nov 7 '18 at 21:16
• @danijar: Provide an example of what you're using. That should help analyse what you're talking about... – Werner Nov 7 '18 at 21:20
• I tried to clarify my question by providing a new example. Could you please take another look at my question and let me know if this is clearer? The code I'm currently using uses \resizebox instead of the imaginary \resizetogether, but doesn't achieve the desired effect since the elements are not scaled by the same factor. – danijar Nov 7 '18 at 22:06