8

I'm trying to avoid page breaks between a sectioning command and a following tcolorbox. With "normal" boxes I can use \nopagebreak, e.g. in the before option:

\documentclass[]{article}
\usepackage[many]{tcolorbox}
\usepackage{lipsum,needspace}
\tracingpages=1
\begin{document}
\lipsum[1-4]

\section{title}
\begin{tcolorbox}[before=\nopagebreak,
                  %breakable, %
                  ]
\rule{1cm}{10\baselineskip}
\lipsum[1]
\end{tcolorbox}

\end{document}

But this fails if I activate the breakable option. There is a break point that I can't identify. Has someone an idea where it is and how I can suppress it? \Needspace helps but one has to know the size of objects at the begin of the tcolorbox.

  • That's a tricky problem. A breakable box tests if there is enough space on the current page to place the first box part. In your case, there is not enough space. Thus a \pagebreak is inserted and splitting starts at the next page. This is fine in most situations, but here a lonely heading is left behind. I do not have an automatic solution for that; one has to put a \pagebreak before the heading by hand in this situation (or make this box unbreakable). – Thomas F. Sturm Dec 5 '14 at 14:41
  • @ThomasF.Sturm: A forced pagebreak before the section is not an option - it is quite possible that there is enough room on the page. What works is something like before=\if@nobreak\enlargethispage{-5cm}\fi, or \if@nobreak\vspace{5cm}\null\vspace{-5cm}\fi but inside \tcb@split@first the \enlargethispage (and the test) no longer works. Is there a place where the size needed for the first split is already known and where one could insert an \enlargethispage or the \vspace with a sensible value? – Ulrike Fischer Dec 5 '14 at 15:33
  • @ThomasF.Sturm: Another idea: could tcolorbox write some informations (minimal size of first block etc) to the aux-file or somewhere else? Then I could use this information in suitable \needspace commands. – Ulrike Fischer Dec 5 '14 at 18:44
  • In this case, the minimal size of the first block is unknown because the rule makes it larger than the available space. It stays unknown and the situation is detected by receiving an empty first block. I could save the available space, but after using this information, the space will have changed and I guess we will end with a neverending ping-pong algorithm. – Thomas F. Sturm Dec 6 '14 at 10:39
  • Maybe, I could implement a warning for the least if a page break happens even if \@nobreaktrue is set. Also, a minimal space after a heading could be checked if a tcolorbox follows; something like \if@nobreak\vspace{5cm}\null\vspace{-5cm}\fi you mentioned before. This would not prevent the critical situation completely, but it will become more unlikely. On the other side, small boxes may flush to the next page more often (but with the heading). – Thomas F. Sturm Dec 6 '14 at 10:46
7

The following patch can be used for tcolorbox version 3.32 and prior. It is incorporated in tcolorbox version 3.33 (2014/12/11) and higher. To revert to the old behavior before 3.33, the option ignore nobreak can be applied.

As I wrote in my comments, I fear that there is no global solution for the problem. But I experimented with a patch which might be helpful for many use cases. The patch code tries to estimate the space needed after a heading. If it is very probable that the box or the first part of the box does not fit on the current page, the \vspace{...}\null\vspace{-...} trick of yours is applied to move everything to the next page including the heading.

The space estimation can fail especially, if the box contains a large unbreakable content as in your example. In this case, a warning is given to detect such problems in long documents. One manual solution is to enlarge the lines before break value to match the unbreakable content. Currently, I do not see an automatic way.

The following code is an experimental patch. If it prooves to be stable, I want to put it into an official version, but there have to be many tests before that. If you see (new) problems with it or if you can improve it, feel free to comment. The main part is \tcb@handle@nobreak, the rest is some slightly changed current code.

\documentclass[]{article}
\usepackage[many]{tcolorbox}
\usepackage{lipsum}
\tracingpages=1

\makeatletter

\def\tcb@do@nobreak@warning{%
  \tcb@warning{Using nobreak failed. Try to enlarge `lines before break' or set page breaks manually}%
}

\def\tcb@handle@nobreak{%
  \tcb@enlbreak@init%
  \tcb@breakat@init%
  \tcb@comp@h@page%
  \tcbdimto\tcb@h@page{\tcb@h@page-\baselineskip}% security surcharge
  \tcb@comp@h@total@standalone%
  \tcb@check@for@final@box%
  \iftcb@final@box%
  \else%
    \ifdim\dimexpr\tcb@h@page-\tcb@h@padding-\tcb@h@padtitle<\kvtcb@breakminlines\baselineskip\relax%
      \tcbdimto\tcb@h@page{\tcb@h@page+3\baselineskip}%
      \vspace{\tcb@h@page}%
      {\parskip\z@\nointerlineskip\noindent}%
      \vspace{-\tcb@h@page}%
    \fi%
  \fi%
}

\def\tcb@prepare@break@operation{%
  \let\tcb@nobreak@warning\@empty%
  \@ifundefined{columnbreak}{\tcb@multicolfalse}{%
    \ifnum\doublecol@number>\z@ %
      \tcb@multicoltrue%
    \else%
      \tcb@multicolfalse%
    \fi}%
  \ifx\kvtcb@float\@empty%
    \if@nobreak%
      \tcb@handle@nobreak%
      \let\tcb@nobreak@warning\tcb@do@nobreak@warning%
    \else%
      \kvtcb@beforebox%
    \fi%
    \def\kvtcb@beforebox{\parskip\z@\nointerlineskip\noindent}%
    \noindent% applies parskip
    \vskip\lineskip{\parskip\z@\par\vskip\z@}\bgroup\@nobreakfalse\addpenalty\z@\egroup% force update vertical list
    \tcb@break@allowedtrue%
    \tcb@enlbreak@init%
  \else%
    \tcb@break@allowedfalse%
  \fi%
}


\def\tcb@split@start{%
  \tcb@breakat@init%
  \tcb@comp@h@page%
  % height calculation
  \tcb@comp@h@total@standalone%
  %
  \let\tcb@split@next=\relax%
  \tcb@check@for@final@box%
  \iftcb@final@box%
    \tcb@drawcolorbox@standalone%
  \else
    \iftcb@break@allowed%
      \ifdim\dimexpr\tcb@h@page-\tcb@h@padding-\tcb@h@padtitle<\kvtcb@breakminlines\baselineskip\relax%
        \iftcb@multicol\columnbreak\else\pagebreak\fi%
        \tcb@nobreak@warning%
        \tcb@enlbreak@init\tcb@break@allowedfalse%
        \tcb@comp@h@page%
        \tcb@check@for@final@box%
        \iftcb@final@box%
          \tcb@drawcolorbox@standalone%
        \else
          \let\tcb@split@next=\tcb@split@first%
        \fi
      \else%
        \let\tcb@split@next=\tcb@split@first%
      \fi
    \else%
      \let\tcb@split@next=\tcb@split@first%
    \fi%
  \fi%
  \tcb@split@next%
}


\def\tcb@split@first{%
  \setcounter{tcbbreakpart}{1}%
  \ifx\kvtcb@float\@empty%
    \iftcb@vfillbeforefirst%
      \let\tcb@before@first=\vfill%
    \else%
    \let\tcb@before@first=\@empty%
    \fi%
    \let\tcb@before@middle=\@empty%
    \let\tcb@before@last=\@empty%
    \iftcb@multicol%
      \long\def\tcb@after@first{\par}% using \columnbreak here kills multicols' algortihm sometimes
      \long\def\tcb@after@middle{\par\vfill}%
    \else%
      \long\def\tcb@after@first{\par\pagebreak\tcb@enlbreak@next}%
      \long\def\tcb@after@middle{\par\vfill\eject\tcb@enlbreak@next}%
    \fi%
    \let\tcb@after@last=\par%
  \else%
    \edef\tcb@before@first{\noexpand\tcb@float@env@begin{tcbfloat}[\kvtcb@float]\noexpand\kvtcb@everyfloat}%
    \let\tcb@before@middle=\tcb@before@first%
    \let\tcb@before@last=\tcb@before@first%
    \let\tcb@after@first=\tcb@float@env@end%
    \let\tcb@after@middle=\tcb@float@env@end%
    \let\tcb@after@last=\tcb@float@env@end%
  \fi%
  %
  \edef\kvtcb@skin@@first{\kvtcb@skin@first}%
  \edef\kvtcb@skin@@middle{\kvtcb@skin@middle}%
  \edef\kvtcb@skin@@last{\kvtcb@skin@last}%
  %
  \let\tcb@h@breaksep@first=\kvtcb@bottomsep%
  \tcbdimto\tcb@h@breaksep@middle{\kvtcb@bottomsep+\kvtcb@topsep}%
  \let\tcb@h@breaksep@last=\kvtcb@topsep%
  \tcbdimto\tcb@h@padding@first{\kvtcb@bbtop@stand+\kvtcb@bbbottom@break+\kvtcb@top@rule@stand+\kvtcb@bottom@rule@break+\tcb@h@breaksep@first+\kvtcb@boxsep*2+\kvtcb@top+\kvtcb@bottom}%
  \tcbdimto\tcb@h@padding@middle{\kvtcb@bbtop@break+\kvtcb@bbbottom@break+\kvtcb@top@rule@break+\kvtcb@bottom@rule@break+\tcb@h@breaksep@middle+\kvtcb@boxsep*2+\kvtcb@top+\kvtcb@bottom}%
  \tcbdimto\tcb@h@padding@last{\kvtcb@bbtop@break+\kvtcb@bbbottom@stand+\kvtcb@top@rule@break+\kvtcb@bottom@rule@stand+\tcb@h@breaksep@last+\kvtcb@boxsep*2+\kvtcb@top+\kvtcb@bottom}%
  %
  \tcb@split@setstate@first%
  %
  \splittopskip=0pt%
  \splitmaxdepth=0pt%
  \edef\tcb@thevfuzz{\the\vfuzz}%
  \edef\tcb@thevbadness{\the\vbadness}%
  %
  \tcb@swap{\tcb@totalupperbox}{\tcb@upperbox}%
  \tcb@swap{\tcb@totallowerbox}{\tcb@lowerbox}%
  %
  \let\tcb@split@next=\relax%
  \tcbdimto\tcb@split@dim{\tcb@h@page-\tcb@h@padding@first-\tcb@h@padtitle}%
  \ifdim\tcb@split@dim<\tcb@h@upper\relax%
    \tcb@split@upper@box%
    \ifdim\wd\tcb@totalupperbox=0pt\relax%
      \iftcb@lowerspace%
        \tcb@sdraw@U%
        \let\tcb@split@next=\tcb@split@SL%
      \else%
        \tcb@split@force@standalone%
        \tcb@drawcolorbox@standalone%
      \fi%
    \else%
      \ifdim\dimexpr\tcb@h@upper=0pt\relax%
        \iftcb@break@allowed
          \tcb@swap{\tcb@totalupperbox}{\tcb@upperbox}%
          \tcb@swap{\tcb@totallowerbox}{\tcb@lowerbox}%
          \tcbdimto\tcb@h@upper{\ht\tcb@upperbox+\dp\tcb@upperbox}%
          \tcbdimto\tcb@h@lower{\ht\tcb@lowerbox+\dp\tcb@lowerbox}%
          \iftcb@multicol\columnbreak\else\pagebreak\fi%
          \tcb@nobreak@warning%
          \tcb@enlbreak@init\tcb@break@allowedfalse%
          \let\tcb@split@next=\tcb@split@start%
        \else%
          \tcb@sdraw@U%
          \let\tcb@split@next=\tcb@split@USL%
        \fi%
      \else%
        \tcb@sdraw@U%
        \let\tcb@split@next=\tcb@split@USL%
      \fi%
    \fi%
  \else%
    \tcb@swap{\tcb@totalupperbox}{\tcb@upperbox}%
    \tcbdimto\tcb@split@dim{\tcb@split@dim-\tcb@h@upper-\tcb@h@segment}
    \ifdim\tcb@split@dim<\kvtcb@breakminlines\baselineskip\relax%
      \tcb@sdraw@U%
      \iftcb@lowerspace%
      \let\tcb@split@next=\tcb@split@SL%
      \else%
      \let\tcb@split@next=\tcb@split@USL%
      \fi%
    \else
      \tcb@split@lower@box%
      \ifdim\wd\tcb@totallowerbox=0pt\relax%
        \tcb@split@force@standalone%
        \tcb@drawcolorbox@standalone%
      \else%
        \tcb@sdraw@USL%
        \let\tcb@split@next=\tcb@split@L%
      \fi%
    \fi%
  \fi%
  \tcb@split@next%
}

\makeatother


\begin{document}
\lipsum[1-4]

\section{title}
\begin{tcolorbox}[enhanced,breakable,%
                  lines before break=10,% commenting this issues a warning
                  ]
\rule{1cm}{10\baselineskip}
\lipsum[1]
\end{tcolorbox}


\clearpage
\lipsum[1-4]

\rule{1cm}{6mm}

\section{Normal case}%
\begin{tcolorbox}[enhanced,breakable]
\lipsum[1]
\end{tcolorbox}

\end{document}

enter image description here

  • I have now tried the patch with a rather large document with a lots of boxes and it reduced around 20 occurences of such breaks to one - and gave a neat warning in the log about it. So it looks quite promising. – Ulrike Fischer Dec 9 '14 at 15:51
  • @UlrikeFischer Good to hear. Currently, I'm doing further tests myself to ensure stability. – Thomas F. Sturm Dec 10 '14 at 16:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.