From 89e8cb34597d8d7d4d317ef775103c25236c2e37 Mon Sep 17 00:00:00 2001 From: jfbu Date: Fri, 5 Feb 2016 14:53:39 +0100 Subject: [PATCH] Address #2262 (latex): avoid pagebreaks after captions of literal blocks This could fix https://github.com/sphinx-doc/sphinx/issues/2262 The 1.3.5 Verbatim environment from sphinx.sty has many places allowing a page break after the caption: from the \smallskip, from the list environment, and from the \MakeFramed: indeed framed package documentation explains that it encourages page breaks above it. The only way to avoid the pagebreaks is to put the caption inside the environment whic is started by \MakeFramed. However, as this environment typesets multiple times its contents, we must inhibit within it the increase of counters (only the literal-block counter is concerned), this is done thanks to a switch provided by the package amsmath which is already loaded by sphinx.sty. modified: sphinx/texinputs/sphinx.sty modified: sphinx/writers/latex.py modified: tests/test_directive_code.py --- sphinx/texinputs/sphinx.sty | 83 ++++++++++++++++++++++++++++++++---- sphinx/writers/latex.py | 4 +- tests/test_directive_code.py | 4 +- 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index a5425e542..6b9494fba 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -163,10 +163,75 @@ } \def\FrameCommand{\mycolorbox} +% Go deep into framed.sty to insert a Title above the frame +% Will be used for literal-block captions. +% \MyCustomFBox, \MyFrameTitle, \MyVerbatimTitle, \setupverbatimcaption + +% The amsmath patches \stepcounter to inhibit stepping via \firstchoice@false +% We will use that, because framed.sty typesets multiple times its contents, +% and we want to put the \captionof in it. +\newif\if@MyFirstFramedPass + +% \MyCustomFBox is for use by \FirstFrameCommand +% It is copied from framed.sty's \CustomFBox, with the difference that +% #1=title/caption is to be set _above_ the top rule, not _below_ +% #1 must be "vertical material" and may be left empty. +% #1=\captionof{literal-block}{foo} is ok for example. +\long\def\MyCustomFBox#1#2#3#4#5#6#7{% + % we set up amsmath (amstext.sty) conditional to inhibit counter stepping + % except in first pass + \if@MyFirstFramedPass\firstchoice@true + \else\firstchoice@false\fi + \leavevmode\begingroup + \setbox\@tempboxa\hbox{% + \color@begingroup + \kern\fboxsep{#7}\kern\fboxsep + \color@endgroup}% + \hbox{% + \lower\dimexpr#4+\fboxsep+\dp\@tempboxa\hbox{% + \vbox{% + #1% TITLE + \hrule\@height#3\relax + \hbox{% + \vrule\@width#5\relax + \vbox{% + \vskip\fboxsep + \copy\@tempboxa + \vskip\fboxsep}% + \vrule\@width#6\relax}% + #2% + \hrule\@height#4\relax}% + }% + }% + \endgroup + \global\@MyFirstFramedPassfalse +} + +\newcommand*\MyFrameTitle {} +\newcommand*\MyVerbatimTitle {} +\newcommand*\setupcaptionforverbatim [2] +{% make \smallskip customizable ? + \def\MyVerbatimTitle{\captionof{#1}{#2}\smallskip }% +} + +\def\FirstFrameCommand{% + % this is inspired from framed.sty v 0.96 2011/10/22 lines 185--190 + % \fcolorbox (see \mycolorbox above) from color.sty uses \fbox. + \def\fbox{\MyCustomFBox{\MyFrameTitle}{}% + \fboxrule\fboxrule\fboxrule\fboxrule}% + % \fcolorbox from xcolor.sty may use rather \XC@fbox. + \let\XC@fbox\fbox + \mycolorbox +} + +% Unneeded %'s after control words removed. \renewcommand{\Verbatim}[1][1]{% % list starts new par, but we don't want it to be set apart vertically - \bgroup\parskip=0pt% - \smallskip% + \bgroup\parskip=0pt + \smallskip + % use customized framed environment + \let\MyFrameTitle\MyVerbatimTitle + \global\@MyFirstFramedPasstrue % The list environement is needed to control perfectly the vertical % space. \list{}{% @@ -177,15 +242,17 @@ \setlength\leftmargin{0pt}% }% \item\MakeFramed {\FrameRestore}% - \small% + \small \OriginalVerbatim[#1]% } \renewcommand{\endVerbatim}{% - \endOriginalVerbatim% - \endMakeFramed% - \endlist% - % close group to restore \parskip - \egroup% + \endOriginalVerbatim + \endMakeFramed + \endlist + % close group to restore \parskip (and \MyFrameTitle) + \egroup + % reset to empty \MyVerbatimTitle + \global\let\MyVerbatimTitle\empty } diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 21ab1549b..bb11c76b0 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1383,8 +1383,8 @@ class LaTeXTranslator(nodes.NodeVisitor): self.in_caption += 1 if self.in_container_literal_block: self.body.append('\\needspace{\\literalblockneedspace}') - self.body.append('\\vspace{\\literalblockcaptiontopvspace}') - self.body.append('\\captionof{literal-block}{') + self.body.append('\\vspace{\\literalblockcaptiontopvspace}%') + self.body.append('\n\\setupcaptionforverbatim{literal-block}{') return self.body.append('\\caption{') diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index 8f3fb3161..894511394 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -64,7 +64,7 @@ def test_code_block_caption_html(app, status, warning): def test_code_block_caption_latex(app, status, warning): app.builder.build_all() latex = (app.outdir / 'Python.tex').text(encoding='utf-8') - caption = '\\captionof{literal-block}{caption \\emph{test} rb}' + caption = '\\setupcaptionforverbatim{literal-block}{caption \\emph{test} rb}' assert caption in latex @@ -229,5 +229,5 @@ def test_literalinclude_caption_html(app, status, warning): def test_literalinclude_caption_latex(app, status, warning): app.builder.build('index') latex = (app.outdir / 'Python.tex').text(encoding='utf-8') - caption = '\\captionof{literal-block}{caption \\textbf{test} py}' + caption = '\\setupcaptionforverbatim{literal-block}{caption \\textbf{test} py}' assert caption in latex