[Tex/LaTex] Column break only if within first column of multicol

conditionalsmulticolpage-breaking

Background

Creating books automatically using LaTeX-escaped user-generated content.

Problem

The book layouts make use of the multi-column package, with the book content separated into sections. If the second section is too long, is written entirely on the subsequent page, regardless of how much space remains after the first section.

Question

How do you force the multicols package to only break to the next column while in the first column, but to not break if within the second column?

Output

The following figure illustrates the intent of the question; the second section should fill as much space in the second column of the first page as possible; overflowing to the second page whatever portion of the second section that does not fit:

Fill Space

Code

A minimal example to recreate the issue:

\documentclass{article}
\usepackage{lipsum}
\usepackage{multicol}

\raggedcolumns

\begin{document}

\begin{multicols*}{2}
\section{First}
\lipsum[1-3]

% Insert column break ONLY IF still within 1st column (e.g. if \lipsum[1-2] above).
% DO NOT insert column break if already in 2nd column (e.g. if \lipsum[1-3] above).
\columnbreak

\section{Second}
\lipsum[4-5]
\end{multicols*}

\end{document}

Attempts

I've tried using multicols* and \raggedcolumns to no avail. I thought that \maxbalancingoverflow would help, but could not get it to parse.

Related

Questions I found useful, but did not offer any simple solutions:

Best Answer

The answer in the related question Detecting current column in multicol should have solved the problem since it was written to detect in which column you are. So my first guess was that

\usepackage{mccolaction}
...
\docolaction{\columnbreak}{}{}

should do the trick.

Unfortunately it doesn't, because the way I had written \docolaction the point that determines in which column we are is the point directly following the executed argument. So executing \columnbreak above would shift it from the first to the second column :-( As there are cases where it is correct to have this order one can't really say that it is wrong as implemented.

So either one needs to redefine \docolaction or do a little bit or reprogramming like this:

\newif\ifinlast
\def\columnbreakunlesslast{%
    \docolaction{\typeout{in first column}\inlastfalse}%
                           {\typeout{in middle column}\inlastfalse}%
                           {\typeout{in last column}\inlasttrue}%
    \unless \ifinlast \columnbreak \fi
 }

Implemented like this it also works with 3 or more columns. E.g. using \lipsum[1-3] we get

enter image description here

And with \lipsum[1-2] a column break happens:

enter image description here

Of course, this code also works with a different number of columns including 2 as as in the OP question. I only gae the example of 3 columns to show that it is not restricted to the two-column situation and can make use of multicols ability to produce more columns.

For reference here is the full code using the mccolaction package (in a version that works with the development version in SVN and the CTAN version of multicol):

\begin{filecontents}{mccolaction.sty}
%    \begin{macrocode}
\ProvidesPackage{mccolaction}
          [2013/07/07 v0.9c  column actions for multicolumn formatting (FMi)]
%    \end{macrocode}
%
%    \begin{macrocode}
\RequirePackage{etoolbox}
\RequirePackage{multicol}[2011/06/27]
%    \end{macrocode}
%
%   Determining the current column in multicols is difficult because
%   (in contrast to the twocolumn mode of standard LaTeX) the
%   multicols columns are determined very late in the game and due to
%   the balancing routine it is not known where a piece of text is
%   going to end up at the time the text is typeset. Only afterwards,
%   when everything has be typeset into a single long galley, that
%   galley is split into individual columns (at the very end in a
%   possibly huge set of trials to balance the column material.
%
%   Therefore the approach taken here is to write out a single line
%   into the .aux file whenever a column is finally typeset:
%\begin{verbatim}
% \mc@col@status{<number>}
%\end{verbatim}
%   The number in the argument denotes the different kind of column: 1
%   for left column 2 for any middle column and 3 for the final column.
%
%   We only set this up for the LR typesetting case here, something
%   similar could be done for the RL version:
%    \begin{macrocode}
%
% there has been a code change after 2011/06 that never made it to CTAN
% but only into the SVN repository
% so we need to provide two different versions:
\@ifpackagelater{multicol}{2011/06/28}
{%
\patchcmd{\LR@column@boxes}{\box\count@}%
         {\protected@write\@auxout{}{\string\mc@col@status
              {\ifmc@firstcol 1\else 2\fi}}%
          \mc@firstcolfalse
          \box\count@}%
         {\typeout{juhu!}}{\typeout{oje!}}%
\patchcmd{\LR@column@boxes}{\box\mult@rightbox}%
         {\protected@write\@auxout{}{\string\mc@col@status{3}}%
          \box\mult@rightbox}%
         {\typeout{juhu!}}{\typeout{oje!}}%
}
{%
\patchcmd{\page@sofar}{\box\count@}%
         {\protected@write\@auxout{}{\string\mc@col@status
              {\ifmc@firstcol 1\else 2\fi}}%
          \mc@firstcolfalse
          \box\count@}%
         {\typeout{juhu!}}{\typeout{oje!}}%
\patchcmd{\page@sofar}{\box\mult@rightbox}%
         {\protected@write\@auxout{}{\string\mc@col@status{3}}%
          \box\mult@rightbox}%
         {\typeout{juhu!}}{\typeout{oje!}}%
}
\newif\ifmc@firstcol
\mc@firstcoltrue
%    \end{macrocode}
%
%   Need to reinitiate \verb=\mc@align@columns= as this was let to
%   the old definition of \verb=\LR@column@boxes=.
%
%    \begin{macrocode}
\LRmulticolcolumns

%   Whenever we want to do something that depends on the current
%   column we execute \verb=\docolaction=. This command takes one
%   optional and three mandatory arguments. The mandatory ones denote
%   what to do if this is a ``left'', ``middle'', or ``right'' column
%   and the optional one is simply there to say what to do if we don't
%   know (default is to use the ``left'' column action in that case).
%
%   We use one counter \verb=\mc@col@check@num= to generate us unique
%   label names. Each time we execute \verb=\docolaction= we increment
%   this counter to get a new name.
%    \begin{macrocode}
\newcount\mc@col@check@num
%    \end{macrocode}

%   The generated ``labels'' are named
%   \verb=\mc@col-\the\mc@col@check@num= and they hold as values the
%   numbers 1, 2, or 3 denoting the current column type.

%    \begin{macrocode}
\newcommand\docolaction[4][1]{%
 \global\advance\mc@col@check@num\@ne
 \edef\mc@col@type{\expandafter\ifx
               \csname mc@col-\the\mc@col@check@num\endcsname\relax
                  0\else
                  \csname mc@col-\the\mc@col@check@num\endcsname
               \fi}%
%    \end{macrocode}
%    We prefix with 0 so that an unknown label (that returns
%   \verb=\relax=) will result in case 0
%    \begin{macrocode}
 \ifcase \mc@col@type\relax
%    \end{macrocode}
%    If column is unknown we use the default action or the action
%   denoted by the optional argument (so that arg can take the value
%   1, 2, 3)
%    \begin{macrocode}
     \ifcase #1\or #2\or#3\or#4\fi   % 0 not known use first col as default
  \or
%    \end{macrocode}
%    Otherwise we know (or think we know) that this is a first, middle,
%   or last column:
%    \begin{macrocode}
     #2%  % 1 First col
  \or
     #3%  % 2 any middle col
  \or
     #4%  % 3 last col
  \else
    \ERROR
  \fi
%    \end{macrocode}
%    But how does the column number get associated with our label? We
%   do do this by writing another line into the aux file at this point:
%    \begin{macrocode}
  \edef\next{\write\@auxout
     {\string\mc@set@col@status{mc@col-\the\mc@col@check@num}%
                               {\mc@col@type}}}%
  \next
}
%    \end{macrocode}
%
%   Because of extra data writing to the aux file the aux file will
%   now contain something like the following after the document is
%   processed the first time:
%\begin{verbatim}
%\relax
%\mc@col@status{1}
%\mc@set@col@status{lcol-1}{0}
%\mc@col@status{2}
%\mc@set@col@status{lcol-2}{0}
%\mc@col@status{3}
%\mc@set@col@status{lcol-3}{0}
%\mc@col@status{1}
%\mc@col@status{2}
%\mc@col@status{3}
%\mc@set@col@status{lcol-4}{0}
%\end{verbatim}
%   The \verb=\mc@col@status= line denotes the column type and has been
%   writting out just before corresponding the column box was placed
%   onto the page.
%   The\verb=\mc@set@col@status= lines have been written out as part
%   of shipping the column boxes out, e.g.,
%   \verb=\mc@set@col@status{lcol-1}{0}= was therefore somewhere within
%   the first column as it appears between \verb=\mc@col@status{1}=
%   and  \verb=\mc@col@status{2}=
%   The second argument in that line is the value used in the previous
%   run (or zero if there was no previous run. We can use this to
%   determine if a rerun is necessary.
%
%   Thus with this knowledge we can set things up to get the labels
%   working.
%
%   When the aux file is read in \verb=\mc@col@status= is used to set
%   \verb=\mc@curr@col@status=:
%
%    \begin{macrocode}
\def\mc@col@status#1{\gdef\mc@curr@col@status{#1}}
%    \end{macrocode}

%   And when \verb=\mc@set@col@status= is executed we can simply set
%   up the label by associating it with the \verb=\mc@curr@col@status=
%   and ignore the second argument:
%    \begin{macrocode}
\def\mc@set@col@status#1#2{%
   \global\expandafter\let\csname #1\endcsname\mc@curr@col@status}
%    \end{macrocode}
%
%   The above definition is being used when the \texttt{.aux} file is
%   read in at the beginning. At the end we need a different
%   definition to test if another typesetting run is needed. There we
%   compare the value used in the current run (stored in the second
%   argument) with the value used on the next run. If those two values
%   differ we set \verb=@tempswa= to false which will trigger the
%   ``Label(s) may have changed'' warning.
%    \begin{macrocode}
\AtEndDocument{\def\mc@set@col@status#1#2{%
     \ifnum #2=\mc@curr@col@status\else
       \@tempswatrue
     \fi}%
}
%    \end{macrocode
\end{filecontents}


\documentclass{article}
\usepackage{lipsum}
%\usepackage{multicol}
\usepackage{mccolaction}

\newif\ifinlast
\def\columnbreakunlesslast{%
    \docolaction{\typeout{in first column}\inlastfalse}%
                           {\typeout{in middle column}\inlastfalse}%
                           {\typeout{in last  column}\inlasttrue}%
    \unless \ifinlast \columnbreak \fi
 }

\raggedcolumns

\begin{document}

\begin{multicols*}{3}
\section{First}
\lipsum[1-2]

% Insert column break ONLY IF still within 1st column (e.g. if \lipsum[1-2] above).
% DO NOT insert column break if already in 2nd column (e.g. if \lipsum[1-3] above).
\columnbreakunlesslast

\section{Second}
\lipsum[4-5]
\end{multicols*}

\end{document}
Related Question