[Tex/LaTex] How to modify columns/column environments so they resize automatically to the largest column

beamercolumnsvertical alignment

This question was originally a duplicate of How can I add vertical space to a beamercolorbox to make it align with another one?. The response to that question was to add a vertical space according to the largest column, as Seamus explains in the comments. I would like, however, to do this in an automatic way.

I rephrase my question.

First, here is the original question, which I leave with minor modifications so that the motivation remains clear. Then, I will analyze and explain the new, rephrased question.


Original question

When using the columns environment in beamer, I sometimes find myself with these situation: two columns with some contents (like a block) whose height are different. The result is that each element appears with its height:

example of columns

I think that if both columns had the same height (the max of all columns), the presentation would be more elegant (at least with the presentation that I am currently creating).

Normally I add some \vspace with a length determined by trial-and-error. Needless to say, this is an annoying process. I would like to force the contents of a column to have the same height as the largest column.

The example above was generated by this code:

\documentclass{beamer}

\usetheme{Darmstadt}
\usefonttheme[onlylarge]{structurebold}
\setbeamerfont*{frametitle}{size=\normalsize,series=\bfseries}
\setbeamertemplate{navigation symbols}{}


\begin{document}

\begin{frame}{A frame}
  \begin{columns}[T]
    \begin{column}{0.5\linewidth}
      \begin{block}{A block}
        Hello
      \end{block}
    \end{column}
    \begin{column}{0.5\linewidth}
      \begin{block}{Another block}
        This one has a larger height since it has an itemize
        \begin{itemize}
        \item item a
        \item item b
        \end{itemize}
      \end{block}
    \end{column}
  \end{columns}
\end{frame}

\end{document}

New question

I have examined beamerbaserequires.sty looking for the definition of the columns and column environment. In order to resize each column, I think it is necessary to rewrite part of the code, since a column is implemented as a minipage without a height parameter. My new question is, as the title says, How to modify columns/column environments so they resize automatically to the largest column ?

My first thoughts on how to achieve this are the following:

  1. Resizing each column to the largest one may not be possible with one pass. It is only when the columns environment is closed that we could know the correct size and at that time it is too late to modify each column.
  2. At the code that "closes" a column could add a vertical space equal to the size of the largest column so far.
  3. The size of the largest column could be written and read from the aux file or some other temporal file, so that, in a second pass, all columns are resized correctly.

These guidelines are probably incorrect, implausible or too difficult to implement, since I have nearly no experience coding in TeX/LaTeX. Any comments regarding these ideas are welcome as well.

PS.
I hesitate whether this question has become too specific (or even too broad), or if it should become a community wiki…

Best Answer

One way to do this is to collect the whole columns content e.g. using the environ package, which stores the environment body in \BODY. Redefine column to measure the natural height of each column as well as remembering the maximum height in a first pass and then typeset the columns in a second pass where a \vspace is inserted with the correct adjustment.

I use here a counter to store the heights in unique macros which are then used to define the \autoheight macro with the right amount. This \autoheight macro must be placed where the extra height is required, e.g. before \end{box}. It can and should be placed in all columns and will be just \vspace*{0pt} for the largest column. It would also be possible to add this macro to all \end{box}, e.g. using the etoolbox package and \preto{\endbox}{\autoheight}. Also the columns environment could be redefined instead of using acolumns so no frame code must be adjusted at all. However, this makes the application less flexible. For example if there are two boxes in one column both would be resized incorrectly. However, this could also be implemented, e.g. by counting the number of boxes per column and spread the height accordantly. (This is left as exercise to the reader ;-) )

Limitations:

  • Does not support verbatim content like lstlistings even when the fragile option is set for the frame.
  • Does not support cascaded columns (yet). This is because some values are set globally.

Both of these could be avoided with some extra effort.

\documentclass{beamer}

\usetheme{Darmstadt}
\usefonttheme[onlylarge]{structurebold}
\setbeamerfont*{frametitle}{size=\normalsize,series=\bfseries}
\setbeamertemplate{navigation symbols}{}

\usepackage{environ}% Required for \NewEnviron, i.e. to read the whole body of the environment
\makeatletter

\newcounter{acolumn}%  Number of current column
\newlength{\acolumnmaxheight}%   Maximum column height


% `column` replacement to measure height
\newenvironment{@acolumn}[1]{%
    \stepcounter{acolumn}%
    \begin{lrbox}{\@tempboxa}%
    \begin{minipage}{#1}%
}{%
    \end{minipage}
    \end{lrbox}
    \@tempdimc=\dimexpr\ht\@tempboxa+\dp\@tempboxa\relax
    % Save height of this column:
    \expandafter\xdef\csname acolumn@height@\roman{acolumn}\endcsname{\the\@tempdimc}%
    % Save maximum height
    \ifdim\@tempdimc>\acolumnmaxheight
        \global\acolumnmaxheight=\@tempdimc
    \fi
}

% `column` wrapper which sets the height beforehand
\newenvironment{@@acolumn}[1]{%
    \stepcounter{acolumn}%
    % The \autoheight macro contains a \vspace macro with the maximum height minus the natural column height
    \edef\autoheight{\noexpand\vspace*{\dimexpr\acolumnmaxheight-\csname acolumn@height@\roman{acolumn}\endcsname\relax}}%
    % Call original `column`:
    \orig@column{#1}%
}{%
    \endorig@column
}

% Save orignal `column` environment away
\let\orig@column\column
\let\endorig@column\endcolumn

% `columns` variant with automatic height adjustment
\NewEnviron{acolumns}[1][]{%
    % Init vars:
    \setcounter{acolumn}{0}%
    \setlength{\acolumnmaxheight}{0pt}%
    \def\autoheight{\vspace*{0pt}}%
    % Set `column` environment to special measuring environment
    \let\column\@acolumn
    \let\endcolumn\end@acolumn
    \BODY% measure heights
    % Reset counter for second processing round
    \setcounter{acolumn}{0}%
    % Set `column` environment to wrapper
    \let\column\@@acolumn
    \let\endcolumn\end@@acolumn
    % Finally process columns now for real
    \begin{columns}[#1]%
        \BODY
    \end{columns}%
}
\makeatother

\begin{document}
\begin{frame}{A frame}
  \begin{acolumns}[T]
    \begin{column}{0.5\linewidth}
      \begin{block}{A block}
        Hello
        \autoheight
      \end{block}
    \end{column}
    \begin{column}{0.5\linewidth}
      \begin{block}{Another block}
        This one has a larger height since it has an itemize
        \begin{itemize}
        \item item a
        \item item b
        \end{itemize}
        \autoheight
      \end{block}
    \end{column}
  \end{acolumns}
\end{frame}

\end{document}

Result

Related Question