diff --git a/.travis.yml b/.travis.yml index fe93e50d2..7549a579d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,11 @@ python: - "3.5" - "pypy" env: - - DOCUTILS=0.11 - - DOCUTILS=0.12 + global: + - TEST=-v + matrix: + - DOCUTILS=0.11 + - DOCUTILS=0.12 install: - pip install -U pip - pip install docutils==$DOCUTILS diff --git a/CHANGES b/CHANGES index 770297c9f..d86e85dc0 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,8 @@ Release 1.5 (in development) Incompatible changes -------------------- +* LaTeX package fancybox is not longer a dependency of sphinx.sty + Features added -------------- @@ -24,6 +26,10 @@ Features added -------------- * Now :confval:`suppress_warnings` accepts following configurations: ``app.add_node``, ``app.add_directive``, ``app.add_role`` and ``app.add_generic_role`` (ref: #2451) +* LaTeX writer allows page breaks in topic contents; and their horizontal + extent now fits in the line width (shadow in margin). Warning-type + admonitions allow page breaks (if very long) and their vertical spacing + has been made more coherent with the one for Hint-type notices. Bugs fixed ---------- @@ -34,6 +40,8 @@ Bugs fixed * #2436: Sphinx does not check version by :confval:`needs_sphinx` if loading extensions failed * #2397: Setup shorthandoff for turkish documents * #2447: VerbatimBorderColor wrongly used also for captions of PDF +* #2456: C++, fix crash related to document merging (e.g., singlehtml and Latex builders). +* #2446: latex(pdf) sets local tables of contents (or more generally topic nodes) in unbreakable boxes, causes overflow at bottom Release 1.4.1 (released Apr 12, 2016) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 6847d5abe..6306ac901 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -572,7 +572,7 @@ class ASTIdentifier(ASTBase): classname=None) key = symbol.get_lookup_key() assert key - pnode['cpp:parentKey'] = key + pnode['cpp:parent_key'] = key pnode += nodes.Text(self.identifier) signode += pnode elif mode == 'lastIsName': @@ -3768,10 +3768,10 @@ class CPPObject(ObjectDescription): raise NotImplementedError() def handle_signature(self, sig, signode): - if 'cpp:parentSymbol' not in self.env.ref_context: - root = self.env.domaindata['cpp']['rootSymbol'] - self.env.ref_context['cpp:parentSymbol'] = root - parentSymbol = self.env.ref_context['cpp:parentSymbol'] + if 'cpp:parent_symbol' not in self.env.ref_context: + root = self.env.domaindata['cpp']['root_symbol'] + self.env.ref_context['cpp:parent_symbol'] = root + parentSymbol = self.env.ref_context['cpp:parent_symbol'] parser = DefinitionParser(sig, self) try: @@ -3783,16 +3783,16 @@ class CPPObject(ObjectDescription): # the possibly inner declarations. name = _make_phony_error_name() symbol = parentSymbol.add_name(name) - self.env.ref_context['cpp:lastSymbol'] = symbol + self.env.ref_context['cpp:last_symbol'] = symbol raise ValueError try: symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) - self.env.ref_context['cpp:lastSymbol'] = symbol + self.env.ref_context['cpp:last_symbol'] = symbol except _DuplicateSymbolError as e: # Assume we are actually in the old symbol, # instead of the newly created duplicate. - self.env.ref_context['cpp:lastSymbol'] = e.symbol + self.env.ref_context['cpp:last_symbol'] = e.symbol if ast.objectType == 'enumerator': self._add_enumerator_to_parent(ast) @@ -3839,13 +3839,13 @@ class CPPClassObject(CPPObject): return _('%s (C++ class)') % name def before_content(self): - lastSymbol = self.env.ref_context['cpp:lastSymbol'] + lastSymbol = self.env.ref_context['cpp:last_symbol'] assert lastSymbol - self.oldParentSymbol = self.env.ref_context['cpp:parentSymbol'] - self.env.ref_context['cpp:parentSymbol'] = lastSymbol + self.oldParentSymbol = self.env.ref_context['cpp:parent_symbol'] + self.env.ref_context['cpp:parent_symbol'] = lastSymbol def after_content(self): - self.env.ref_context['cpp:parentSymbol'] = self.oldParentSymbol + self.env.ref_context['cpp:parent_symbol'] = self.oldParentSymbol def parse_definition(self, parser): return parser.parse_declaration("class") @@ -3859,13 +3859,13 @@ class CPPEnumObject(CPPObject): return _('%s (C++ enum)') % name def before_content(self): - lastSymbol = self.env.ref_context['cpp:lastSymbol'] + lastSymbol = self.env.ref_context['cpp:last_symbol'] assert lastSymbol - self.oldParentSymbol = self.env.ref_context['cpp:parentSymbol'] - self.env.ref_context['cpp:parentSymbol'] = lastSymbol + self.oldParentSymbol = self.env.ref_context['cpp:parent_symbol'] + self.env.ref_context['cpp:parent_symbol'] = lastSymbol def after_content(self): - self.env.ref_context['cpp:parentSymbol'] = self.oldParentSymbol + self.env.ref_context['cpp:parent_symbol'] = self.oldParentSymbol def parse_definition(self, parser): ast = parser.parse_declaration("enum") @@ -3912,7 +3912,7 @@ class CPPNamespaceObject(Directive): def run(self): env = self.state.document.settings.env - rootSymbol = env.domaindata['cpp']['rootSymbol'] + rootSymbol = env.domaindata['cpp']['root_symbol'] if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): symbol = rootSymbol stack = [] @@ -3927,8 +3927,8 @@ class CPPNamespaceObject(Directive): ast = ASTNamespace(name, None) symbol = rootSymbol.add_name(ast.nestedName, ast.templatePrefix) stack = [symbol] - env.ref_context['cpp:parentSymbol'] = symbol - env.temp_data['cpp:namespaceStack'] = stack + env.ref_context['cpp:parent_symbol'] = symbol + env.temp_data['cpp:namespace_stack'] = stack return [] @@ -3954,14 +3954,14 @@ class CPPNamespacePushObject(Directive): self.warn(e.description) name = _make_phony_error_name() ast = ASTNamespace(name, None) - oldParent = env.ref_context.get('cpp:parentSymbol', None) + oldParent = env.ref_context.get('cpp:parent_symbol', None) if not oldParent: - oldParent = env.domaindata['cpp']['rootSymbol'] + oldParent = env.domaindata['cpp']['root_symbol'] symbol = oldParent.add_name(ast.nestedName, ast.templatePrefix) - stack = env.temp_data.get('cpp:namespaceStack', []) + stack = env.temp_data.get('cpp:namespace_stack', []) stack.append(symbol) - env.ref_context['cpp:parentSymbol'] = symbol - env.temp_data['cpp:namespaceStack'] = stack + env.ref_context['cpp:parent_symbol'] = symbol + env.temp_data['cpp:namespace_stack'] = stack return [] @@ -3977,7 +3977,7 @@ class CPPNamespacePopObject(Directive): def run(self): env = self.state.document.settings.env - stack = env.temp_data.get('cpp:namespaceStack', None) + stack = env.temp_data.get('cpp:namespace_stack', None) if not stack or len(stack) == 0: self.warn("C++ namespace pop on empty stack. Defaulting to gobal scope.") stack = [] @@ -3986,17 +3986,17 @@ class CPPNamespacePopObject(Directive): if len(stack) > 0: symbol = stack[-1] else: - symbol = env.domaindata['cpp']['rootSymbol'] - env.ref_context['cpp:parentSymbol'] = symbol - env.temp_data['cpp:namespaceStack'] = stack + symbol = env.domaindata['cpp']['root_symbol'] + env.ref_context['cpp:parent_symbol'] = symbol + env.temp_data['cpp:namespace_stack'] = stack return [] class CPPXRefRole(XRefRole): def process_link(self, env, refnode, has_explicit_title, title, target): - parent = env.ref_context.get('cpp:parentSymbol', None) + parent = env.ref_context.get('cpp:parent_symbol', None) if parent: - refnode['cpp:parentKey'] = parent.get_lookup_key() + refnode['cpp:parent_key'] = parent.get_lookup_key() if refnode['reftype'] == 'any': # Assume the removal part of fix_parens for :any: refs. # The addition part is done with the reference is resolved. @@ -4055,12 +4055,12 @@ class CPPDomain(Domain): 'enumerator': CPPXRefRole() } initial_data = { - 'rootSymbol': Symbol(None, None, None, None, None, None), + 'root_symbol': Symbol(None, None, None, None, None, None), 'names': {} # full name for indexing -> docname } def clear_doc(self, docname): - rootSymbol = self.data['rootSymbol'] + rootSymbol = self.data['root_symbol'] rootSymbol.clear_doc(docname) for name, nDocname in list(self.data['names'].items()): if nDocname == docname: @@ -4069,12 +4069,12 @@ class CPPDomain(Domain): def process_doc(self, env, docname, document): # just for debugging # print(docname) - # print(self.data['rootSymbol'].dump(0)) + # print(self.data['root_symbol'].dump(0)) pass def merge_domaindata(self, docnames, otherdata): - self.data['rootSymbol'].merge_with(otherdata['rootSymbol'], - docnames, self.env) + self.data['root_symbol'].merge_with(otherdata['root_symbol'], + docnames, self.env) ourNames = self.data['names'] for name, docname in otherdata['names'].items(): if docname in docnames: @@ -4102,8 +4102,8 @@ class CPPDomain(Domain): warner.warn('Unparseable C++ cross-reference: %r\n%s' % (target, str(e.description))) return None, None - parentKey = node.get("cpp:parentKey", None) - rootSymbol = self.data['rootSymbol'] + parentKey = node.get("cpp:parent_key", None) + rootSymbol = self.data['root_symbol'] if parentKey: parentSymbol = rootSymbol.direct_lookup(parentKey) if not parentSymbol: @@ -4152,7 +4152,7 @@ class CPPDomain(Domain): return [] def get_objects(self): - rootSymbol = self.data['rootSymbol'] + rootSymbol = self.data['root_symbol'] for symbol in rootSymbol.get_all_symbols(): if symbol.declaration is None: continue diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 86f470a64..175344ad9 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -8,17 +8,23 @@ \NeedsTeXFormat{LaTeX2e}[1995/12/01] \ProvidesPackage{sphinx}[2010/01/15 LaTeX package (Sphinx markup)] +\ifx\directlua\undefined\else +% if compiling with lualatex 0.85 or later load compatibility patch issued by +% the LaTeX team for older packages relying on \pdf named primitives. + \IfFileExists{luatex85.sty}{\RequirePackage{luatex85}}{} +\fi + \@ifclassloaded{memoir}{}{\RequirePackage{fancyhdr}} \RequirePackage{textcomp} -\RequirePackage{fancybox} \RequirePackage{titlesec} \RequirePackage{tabulary} \RequirePackage{makeidx} +% For framing code-blocks and warning type notices, and shadowing topics \RequirePackage{framed} +\newif\ifSphinx@inframed % flag set if we are in a framed environment \RequirePackage{ifthen} -%The xcolor package draws better fcolorboxes -%around verbatim code +% The xcolor package draws better fcolorboxes around verbatim code \IfFileExists{xcolor.sty}{ \RequirePackage{xcolor} }{ @@ -242,17 +248,101 @@ \setlength\partopsep{0pt}% \setlength\leftmargin{0pt}% }% - \item\MakeFramed {\FrameRestore}% + \item + % use a minipage if we are already inside a framed environment + \relax\ifSphinx@inframed\noindent\begin{\minipage}{\linewidth}\fi + \MakeFramed {\FrameRestore}% \small \OriginalVerbatim[#1]% } \renewcommand{\endVerbatim}{% \endOriginalVerbatim \endMakeFramed + \ifSphinx@inframed\end{minipage}\fi \endlist % LaTeX environments always revert local changes on exit, here e.g. \parskip } +% define macro to frame contents and add shadow on right and bottom +\def\Sphinx@shadowsep {5\p@} % \p@ means "pt " +\def\Sphinx@shadowsize {4\p@} +\def\Sphinx@shadowrule {\fboxrule} +\long\def\Sphinx@ShadowFBox#1{% + \leavevmode\begingroup + % first we frame the box #1 + \setbox\@tempboxa + \hbox{\vrule\@width\Sphinx@shadowrule + \vbox{\hrule\@height\Sphinx@shadowrule + \kern\Sphinx@shadowsep + \hbox{\kern\Sphinx@shadowsep #1\kern\Sphinx@shadowsep}% + \kern\Sphinx@shadowsep + \hrule\@height\Sphinx@shadowrule}% + \vrule\@width\Sphinx@shadowrule}% + % Now we add the shadow, like \shadowbox from fancybox.sty would do + \dimen@\dimexpr.5\Sphinx@shadowrule+\Sphinx@shadowsize\relax + \hbox{\vbox{\offinterlineskip + \hbox{\copy\@tempboxa\kern-.5\Sphinx@shadowrule + % add shadow on right side + \lower\Sphinx@shadowsize + \hbox{\vrule\@height\ht\@tempboxa \@width\dimen@}% + }% + \kern-\dimen@ % shift back vertically to bottom of frame + % and add shadow at bottom + \moveright\Sphinx@shadowsize + \vbox{\hrule\@width\wd\@tempboxa \@height\dimen@}% + }% + % move left by the size of right shadow so shadow adds no width + \kern-\Sphinx@shadowsize + }% + \endgroup +} + +% use framed.sty to allow page breaks in frame+shadow +% works well inside Lists and Quote-like environments +% produced by ``topic'' directive (or local contents) +% could nest if LaTeX writer authorized it +\newenvironment{SphinxShadowBox} + {\def\FrameCommand {\Sphinx@ShadowFBox }% + % configure framed.sty not to add extra vertical spacing + \OuterFrameSep \z@skip + % the \trivlist will add the vertical spacing on top and bottom which is + % typical of center environment as used in Sphinx <= 1.4.1 + % the \noindent has the effet of an extra blank line on top, to + % imitate closely the layout from Sphinx <= 1.4.1; the \FrameHeightAdjust + % will put top part of frame on this baseline. + \def\FrameHeightAdjust {\baselineskip}% + \trivlist\item\noindent + % use a minipage if we are already inside a framed environment + \ifSphinx@inframed\begin{minipage}{\linewidth}\fi + \MakeFramed {\Sphinx@inframedtrue + % framed.sty puts into "\width" the added width (=2shadowsep+2shadowrule) + % adjust \hsize to what the contents must use + \advance\hsize-\width + % adjust LaTeX parameters to behave properly in indented/quoted contexts + \FrameRestore + % typeset the contents as in a minipage (Sphinx <= 1.4.1 used a minipage and + % itemize/enumerate are therein typeset more tightly, we want to keep + % that). We copy-paste from LaTeX source code but don't do a real minipage. + \@pboxswfalse + % for footnotes, but Sphinx inactivates footnotes in topics + \def\@mpfn{mpfootnote}\def\thempfn{\thempfootnote}\c@mpfootnote\z@ + \let\@footnotetext\@mpfootnotetext + \let\@listdepth\@mplistdepth \@mplistdepth\z@ + \@minipagerestore + \@setminipage + }% + }% + {% insert the "endminipage" code + \par\unskip + % handle (currently non existing) minipage style footnotes + \ifvoid\@mpfootins\else + \vskip\skip\@mpfootins\normalcolor\footnoterule\unvbox\@mpfootins + \fi + \@minipagefalse + \endMakeFramed + \ifSphinx@inframed\end{minipage}\fi + \endtrivlist + } % \moduleauthor{name}{email} \newcommand{\moduleauthor}[2]{} @@ -316,23 +406,49 @@ % Notices / Admonitions % -\newlength{\py@noticelength} -\newcommand{\py@heavybox}{ - \setlength{\fboxrule}{1pt} - \setlength{\fboxsep}{6pt} - \setlength{\py@noticelength}{\linewidth} - \addtolength{\py@noticelength}{-2\fboxsep} - \addtolength{\py@noticelength}{-2\fboxrule} - %\setlength{\shadowsize}{3pt} - \noindent\Sbox - \minipage{\py@noticelength} -} -\newcommand{\py@endheavybox}{ - \endminipage - \endSbox - \fbox{\TheSbox} -} +% Code adapted from framed.sty's "snugshade" environment. +% Nesting works (inner frames do not allow page breaks). +\newcommand{\py@heavybox}{\par + \setlength{\FrameRule}{\p@}% 1pt + \setlength{\FrameSep}{\dimexpr.6\baselineskip-\FrameRule\relax} + % configure framed.sty's parameters to obtain same vertical spacing + % as for "light" boxes. We need for this to manually insert parskip glue and + % revert a skip done by framed before the frame. + \setlength{\OuterFrameSep}{0pt} + \vspace{\FrameHeightAdjust} + % copied/adapted from framed.sty's snugshade + \def\FrameCommand##1{\hskip\@totalleftmargin + \fboxsep\FrameSep \fboxrule\FrameRule\fbox{##1}% + \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth}% + % use a minipage if we are already inside a framed environment + \ifSphinx@inframed + \noindent\begin{minipage}{\linewidth} + \else + \vspace{\parskip} + \fi + \MakeFramed {\Sphinx@inframedtrue + \advance\hsize-\width \@totalleftmargin\z@ \linewidth\hsize + % minipage initialization copied from LaTeX source code. + \@pboxswfalse + % for footnotes + \def\@mpfn{mpfootnote}\def\thempfn{\thempfootnote}\c@mpfootnote\z@ + \let\@footnotetext\@mpfootnotetext + \let\@listdepth\@mplistdepth \@mplistdepth\z@ + \@minipagerestore + \@setminipage }% + } +\newcommand{\py@endheavybox}{% + \par\unskip + % handles footnotes + \ifvoid\@mpfootins\else + \vskip\skip\@mpfootins\normalcolor\footnoterule\unvbox\@mpfootins + \fi + \@minipagefalse\endMakeFramed + \ifSphinx@inframed\end{minipage}\fi + % arrange for similar spacing below frame as for "light" boxes. + \vskip .4\baselineskip + } \newcommand{\py@lightbox}{% \par\allowbreak diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index d08ab77a1..7ce35b80f 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -706,14 +706,11 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_topic(self, node): self.in_minipage = 1 - self.body.append('\\setbox0\\vbox{\n' - '\\begin{minipage}{0.95\\linewidth}\n') + self.body.append('\n\\begin{SphinxShadowBox}\n') def depart_topic(self, node): self.in_minipage = 0 - self.body.append('\\end{minipage}}\n' - '\\begin{center}\\setlength{\\fboxsep}{5pt}' - '\\shadowbox{\\box0}\\end{center}\n') + self.body.append('\\end{SphinxShadowBox}\n') visit_sidebar = visit_topic depart_sidebar = depart_topic