Given 3 columns, a tcolorbox box starts in column 2. Why?

There are limits to what is possible in the interaction between two packages that both try to split a galley into columns without knowing much about each other, e.g., if multicol runs in balancing mode it does its balancing only after any tcolorbox inside has done its magic and thus will only see the breakpoints left by tcolorbox. Thus there is not much chance for automation unless the two packages are really interconnected (ie one implements the features of the other) which is not going to happen. So that means here one has to supply the breakpoints for tcolorboxmanually.

However, in the case of multicols* the situation is somewhat different as tcolorbox could know the available space. It does however simply assumes that it will be \textheight and therefore shows the undesired behavior. Don't think one can call this a bug but it is an unnecessary limitation given that tcolorboxalready tries to be multicol-aware.

Fact is that the available space is known (or could be): it is \@colroom minus whatever has been already typeset in the multicol environment, i.e., \pagetotal(as long as we are in the first column). If we are further down then some level of adjustement is needed but as the code below shows, it seems to be doable.

Now I don't know much about tcolorbox internals (in fact I have looked at the code for the first time today) so my "fix" may need some further adjustments, but the direction it probably ok.

So here is the code and test file:

\documentclass{article}
\usepackage{lipsum,multicol}
\usepackage[breakable]{tcolorbox}

\makeatletter
\newif\iftcb@latermulticol  % are we in a later column?

% remaining height
\def\tcb@comp@h@page{%
  \tcb@breakat@next%
  \ifdim\tcb@breakat@dim>0pt\relax%
    \tcbdimto\tcb@h@page{\tcb@breakat@dim-\kvtcb@shrinkbreakgoal}%
  \else%
    \ifx\kvtcb@float\@empty%
      \iftcb@multicol%
      %
      % currently tcolorbox always assumes that \textheight is available
      %
      %        \tcbdimto\tcb@h@page{\textheight-\kvtcb@shrinkbreakgoal}%
      %
      % but we do know what is available inside a multicol:
      %
      % it is \@colroom minus what is already set (which is \pagetotal)
      %
      % However this is only true for the first column as multicol scans all
      % column material and only then breaks it into columns
      % So for the next columns we need to tell tcolorbox that the value
      % is \@colroom (without any substractions)
      %
      % Thus we distinguish the two cases via a boolean
      %
        \iftcb@latermulticol
          \tcbdimto\tcb@h@page{\@colroom-\kvtcb@shrinkbreakgoal}%
        \else
          \tcb@latermulticoltrue
          \ifdim\pagegoal=16383.99998pt
            \tcbdimto\tcb@h@page{\@colroom-\kvtcb@shrinkbreakgoal}%
          \else%
      %
          % If we start not in the first column then \pagetotal is already
          % the material for more than a column so we substract \@colroom
          % until we are in range (which is not quite accurate but close)
      %    
            \@tempdima\pagetotal
            \@whiledim \@tempdima>\@colroom \do{\advance\@tempdima-\@colroom}%
            \tcb@comp@compress%
            \tcbdimto\tcb@h@page{\@colroom-\@tempdima+\tcb@compress@height -\kvtcb@shrinkbreakgoal}%
          \fi%
        \fi
      %
      % end of mod
      %
      \else%
        \ifdim\pagegoal=16383.99998pt
          \tcbdimto\tcb@h@page{\vsize-\kvtcb@shrinkbreakgoal}% detects floating objects
        \else%
          \tcb@comp@compress%
          \tcbdimto\tcb@h@page{\pagegoal-\pagetotal+\tcb@compress@height-\kvtcb@shrinkbreakgoal}%
        \fi%
      \fi%
    \else%
      \tcbdimto\tcb@h@page{\textheight-\kvtcb@shrinkbreakgoal}%
    \fi%
  \fi%
}
\makeatother

\begin{document}

\begin{multicols*}{3}
  A is for Amy who fell down the stairs.

  \begin{tcolorbox}[title={Lorem Ipsum},breakable]
    \lipsum[1]
  \end{tcolorbox}

  B is for Basil assaulted by bears.
\end{multicols*}

\begin{multicols*}{3}
  Starting with tcolorbox in the second column \ldots
  Starting with tcolorbox in the second column \ldots
  Starting with tcolorbox in the second column \ldots
  Starting with tcolorbox in the second column \ldots
  Starting with tcolorbox in the second column \ldots
  Starting with tcolorbox in the second column \ldots
  Starting with tcolorbox in the second column \ldots

  \lipsum[1]

  \begin{tcolorbox}[title={Lorem Ipsum},breakable]
    \lipsum[1]
  \end{tcolorbox}

  B is for Basil assaulted by bears.
\end{multicols*}


The balancing multicol of course still fails without manual help as
that would require both packages interact with each other

\begin{multicols}{3}
  A is for Amy who fell down the stairs.

  \begin{tcolorbox}[title={Lorem Ipsum},breakable]
    \lipsum[1]
  \end{tcolorbox}

  B is for Basil assaulted by bears.
\end{multicols}

some more text

\end{document}

If we typeset this then we get the following three pages:

enter image description here enter image description here enter image description here


Based on Frank Mittelbachs patch, I experimented a lot to get an algorithm which can do the breaking inside a multicols environment in a fully automated way. But I found too many situations which fool an automatism in a way which is worse than not having such an automatism. Meanwhile, I do not see any success in future.

Nevertheless, a half-automated breaking is possible which is superior to the old implementation. I adapted the patch of Frank Mittelbach and implemented a changed version for tcolorbox from version 4.10 (2017/07/05).

The main (new) characteristics are:

  • The default break height is the current column height at begin of breaking. If the height column height changes from one page to the next, the default break height is not changed (actually, it changes, but not for the first column on the next page).

  • The height of the first box part is estimated as in Frank Mittelbachs patch. There is a fair change that this estimation gives a too-large value causing the first box part to slip from the current column to the next one.

To compensate the possible named shortcuts, a break at can be added. break at also was modified to support easier corrections.

Let's look at some example code:

\documentclass{article}
\usepackage{lipsum,multicol}
\usepackage[showframe]{geometry}
\usepackage[breakable]{tcolorbox}

\begin{document}

\begin{multicols*}{3}
  A is for Amy who fell down the stairs.

  \begin{tcolorbox}[title={Lorem Ipsum},breakable]
    \lipsum[1-2]
  \end{tcolorbox}

  B is for Basil assaulted by bears.
\end{multicols*}

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

  \begin{tcolorbox}[title={Lorem Ipsum},breakable]
    \lipsum[1]
  \end{tcolorbox}

  B is for Basil assaulted by bears.
\end{multicols*}

The two examples above worked without manual help, but this will be true
only for lucky situations. If a multicols environment starts on one page
with previous content (like this one) and continues to a second page,
the automatism will fail.
In this case, use the \texttt{break at} option:

\begin{multicols*}{3}
  A is for Amy who fell down the stairs.
  \lipsum[1-2]

  \begin{tcolorbox}[title={Lorem Ipsum},breakable,height fixed for=first and middle,
    break at=-\baselineskip/0pt/\textheight,
  ]
    \lipsum[1-5]
  \end{tcolorbox}

  B is for Basil assaulted by bears.
\end{multicols*}

\end{document}

enter image description here enter image description here enter image description here enter image description here

The first two multicols* environments with an embedded breakable tcolorbox did not need any manual corrections, but the third one is tricky. Here, the following was added to get the (good) result:

break at=-\baselineskip/0pt/\textheight

The meaning is as follows:

  • -\baselineskip: The first column is sized according to the estimated space minus one \baselineskip, because the actual space is smaller than the estimated one. Prior to version 4.10 (2017/07/05), negative values had the same meaning as 0pt.

  • 0pt: The second column is sized to get the current column height. Prior to version 4.10 (2017/07/05), 0pt meant \textheight.

  • \textheight: The third column and all following columns are sized to \textheight, because on our example page 4 the height of the columns changes.


It seems that a paragraph before the tcolorbox inside a multicol environment breaks the way tcolorbox computes the height of the first fragment and places it on the second column. I think this a bug but I'll wait for other comments/opinions. In the mean time the undesired behaviour can be avoid with a manually computed break at= option.

\documentclass{article}
\usepackage{lipsum,multicol}
\usepackage[breakable]{tcolorbox}
\begin{document}

\begin{multicols}{3}
A is for Amy who fell down the stairs.

\begin{tcolorbox}[title={Lorem Ipsum}, breakable, break at=18cm/0pt]
  \lipsum[1-2]
\end{tcolorbox}

B is for Basil assaulted by bears.
\end{multicols}

\end{document}

enter image description here