[Tex/LaTex] How to get “nonbreaking vertical space”

mdframedpage-breakingspacing

Using mdframed or tcolorbox (my existing code base uses mdframed, I'm interested in moving to tcolorbox), I want to have three things:

  • vertical space between the pretty box and the next paragraph, but
  • no page break or column break in that vertical space, at least one line of text must be present (two would be better, but I'll settle for one for now), and
  • I don't want to break the pretty box.

Starting with this MWE:

\documentclass[letterpaper,10pt,twocolumn,twoside,extrafontsizes]{article}
\usepackage{lipsum}
\usepackage{fp}
\usepackage{mdframed}
\usepackage{printlen}

\newlength{\testskip}
\setlength{\testskip}{1\baselineskip plus 0.5\baselineskip minus 0.5\baselineskip}

\newenvironment{test}{
\begin{mdframed}
}{
\end{mdframed}
}

\title{Suppressing Page Breaks MWE}
\begin{document}
\maketitle
\lipsum[1]
\lipsum[2]
\lipsum[3]
\lipsum[4]
\lipsum[5]
\begin{test}
Testing 1 mdframed
\end{test}
\lipsum[6]
\lipsum[7]
\end{document}

Adding \nopagebreak gets me part way.

\newenvironment{test}{
\begin{mdframed}
}{
\end{mdframed}\nopagebreak
}

This pulls the paragraphs a bit closer together to make room for one line of text and copies the line of text from the next page. If I add \vskip0.5\baselineskip above \lipsum[5] there isn't enough space to reduce, and the mdframed gets pushed to the next page.

So far, so good… but I cannot find a way to add vertical space between the mdframed and the next paragraph. Inserting \vskip0.5\baselineskip or vspace{0.5\baselineskip} apparently not only inserts space but says it's a wonderful place for a page break.

It appears the skipbelow parameter of mdframed does a \vskip, so that leaves me in the same situation.

I know of the needspace package, but I can't predict how big the mdframed is going to be. If I need to I think I can store the mdframed height in the aux file and use that when laying out, but I'd rather not.

(When I try using tcolorbox in a similar manner it looks like \nopagebreak doesn't do anything at all — either there is enough room for a line of text, or there is not, and that's all there is to it.)

It appears also that while this did work at the page break, it didn't seem to make a difference at the column break (preamble unchanged)

\title{Suppressing Page Breaks MWE}
\begin{document}
\maketitle
\lipsum[1]
\vskip4\testskip
\lipsum[2]
\begin{test}
Testing 1 mdframed
\end{test}
\lipsum[3]
\lipsum[4]
\lipsum[5]
\lipsum[6]
\lipsum[7]
\end{document}

(For context, this is for a game reference book with many statblocks — name and a set of statistics about the thing — that I don't want to break up because reading stats across page boundaries is hard. Right now I'm adjusting them by hand, inserting or removing vertical space and breaks before or after the game entities as needed. I'd like to automate this more, and if I can get mdframed to allow space after it that doesn't cause a page break or column break that would do what I'm aiming for. Yes, it's a bit ugly, but it's eminently usable for my purpose.)

Late-Night Ah-Ha!… that didn't work

Obviously, wrapping the mdframed with a minipage should let me put some vertical space after the mdframed and then a \nopagebreak after!

\newenvironment{test}{
\noindent\begin{minipage}{\linewidth}
\begin{mdframed}
}{
\end{mdframed}\bigskip\end{minipage}\nopagebreak
}

did not do as I had hoped. I think either the skip or the minipage still causes the nopagebreak to be ignored.

2017-01-15 Update — \needspace acting oddly

The needspace solution suggested by @Schweinebacke almost worked, but I find cases where it doesn't. I cannot yet replicate them, but I can describe the symptoms.

  • I have in front of me a page with a displayed \pagetotal of 461.9377pt and a \pagegoal of 682pt.
  • The dimension passed to \needspace is 138.37pt. As far as I can tell, this should fit (461pt+138pt = about 600pt; about 80pt, over an inch, left over).
  • Block got moved to the next page, and after the block is output I have a \pagetotal of 601.18pt (which is what it should have been on the previous page, had the block appeared there).

This does not happen consistently. Most often, \needspace works as expected: the block gets moved and the \pagetotal is reset to 0 before the block is output (i.e. I have in front of me one where the space needed was 136.55pt, \pagetotal after the block is 124.55pt (I wanted "height of block plus one line of text, \bigskip is about 12pt, so this all adds up more or less).

It looks as though \needspace determines the requested space is available, then the entire thing gets moved despite there evidently being enough space.

Am I doing something wrong with \needspace in my answer below?

2017-01-18 Update: Why use \needspace at all?

Browsing other questions, I stumbled on something I perhaps should have considered earlier… these blocks are being used much as fancy — and big — headings. This includes possibly being included in the Table of Contents.

Is there any reason I couldn't use \@startsection? Treat it like a section (positive space above and below) with a potentially big section heading, then call it done.

Simple answer… it doesn't work the way I need. I could use it to, say, draw a pretty frame around the section heading (useful to know), but the other elements that I need (title and body content in the frame) really aren't suited for section-style semantics. Which I would have realized had I thought about it for a moment.

Best Answer

mdframed (and tcolorbox) have their own page break algorithms to allow breaks in the boxes. So it is a little bit complicated to put them into another box. But you can put the contents of a mdframed into a box and output this box inside the mdframed. You can measure the height and depth of this box and use the result (and some extra space) for needspace:

\documentclass[letterpaper,10pt,twocolumn,twoside]{article}
\usepackage{lipsum}
\usepackage{mdframed}

\newlength{\testskip}
\setlength{\testskip}{1\baselineskip plus 0.5\baselineskip minus 0.5\baselineskip}

\usepackage{needspace}
\newsavebox\mybox
\newenvironment{test}{%
  \par
  \begin{lrbox}{\mybox}
}{
  \end{lrbox}
  \needspace{\dimexpr \ht\mybox+\dp\mybox+\testskip+2\fboxrule+\fboxsep+\baselineskip\relax}
  \begin{mdframed}\usebox\mybox\end{mdframed}
  \vskip\testskip
}

\title{Suppressing Page Breaks MWE}
\begin{document}
\maketitle
\lipsum[1]
\lipsum[2]
\lipsum[3]
\lipsum[4]
\lipsum[5]
\begin{test}
Testing 1 mdframed
\end{test}
\lipsum[6]
\lipsum[7]
\end{document}

Nevertheless, if you do not want a page break inside mdframed, there is no need to even use mdframed. You can simply put the text in a box and frame it yourself using whatever kind of frame you want:

\documentclass[letterpaper,10pt,twocolumn,twoside]{article}
\usepackage{lipsum}

\newlength{\testskip}
\setlength{\testskip}{1\baselineskip plus 0.5\baselineskip minus 0.5\baselineskip}

\newsavebox\mybox
\newenvironment{test}{%
  \par
  \begin{lrbox}{\mybox}
    \minipage[b]{\dimexpr \linewidth-\fboxsep-\fboxrule}
}{
    \endminipage
  \end{lrbox}
  \noindent\usebox\mybox\hskip\fboxsep\rule[-\dp\mybox]{\fboxrule}{\dimexpr\ht\mybox+\dp\mybox}\linebreak\nopagebreak
  \rule[\ht\strutbox]{\linewidth}{\fboxrule}
  \par\nobreak\vskip\testskip\nobreak
}

\title{Suppressing Page Breaks MWE}
\begin{document}
\maketitle
\lipsum[1]
\lipsum[2]
\lipsum[3]
\lipsum[4]
\lipsum[5]
\begin{test}
\lipsum[1] 
\end{test}
\lipsum[6]
\lipsum[7]
\end{document}
Related Question