Skip to content

LaTeX: figures inside theorem-style boxes are written as floats ("Not in outer par mode") #169

Open

Description

@mmcky

A figure with a caption inside a prf: directive box, such as prf:theorem or prf:lemma, breaks the LaTeX (PDF) build. The HTML build is unaffected.

! LaTeX Error: Not in outer par mode.
l.100 \begin{figure}[htbp]

sphinx-exercise had the same bug, reported in executablebooks/sphinx-exercise#94 and addressed in executablebooks/sphinx-exercise#95.

Cause

Sphinx's LaTeX writer increments self.no_latex_floats whenever it opens one of its own admonitions (visit_admonition, _visit_named_admonition, visit_seealso), and visit_figure then writes \begin{figure}[H] instead of using figure_align, so the figure stays inside the box. sphinx-proof opens the same sphinxadmonition environment by appending latex_admonition_start in visit_enumerable_node and visit_unenumerable_node (sphinx_proof/nodes.py) but never touches the counter, so a figure inside the box is written as a floating \begin{figure}[htbp], which LaTeX rejects there.

This affects every directive in PROOF_TYPES (theorem, lemma, definition, algorithm and the rest), numbered or :nonumber:. prf:proof is not affected, because it wraps a plain docutils admonition node that Sphinx's own visitor handles. visit_figure checks this counter in Sphinx 6 through 9.

Minimal reproduction

sphinx-proof 0.4.0 (current main) and Sphinx 7.4.7, with extensions = ["sphinx_proof"] in conf.py and any image saved as plot.png:

Repro
=====

.. note::

   .. figure:: plot.png

      Caption inside a note

.. prf:theorem:: A theorem
   :label: thm-1

   .. figure:: plot.png

      Caption inside a theorem

.. prf:lemma:: An unnumbered lemma
   :label: lem-1
   :nonumber:

   .. figure:: plot.png

      Caption inside an unnumbered lemma

Running sphinx-build -b latex writes \begin{figure}[H] for the figure in the note but \begin{figure}[htbp] for the theorem and the lemma, and latexmk -pdf in the LaTeX build directory stops at the first of them with the error above.

Proposed patch

Do what Sphinx does in the two LaTeX visit/depart pairs in sphinx_proof/nodes.py:

--- a/sphinx_proof/nodes.py
+++ b/sphinx_proof/nodes.py
@@ -31,6 +31,7 @@
         docname = find_parent(self.builder.env, node, "section")
         self.body.append("\\label{" + f"{docname}:{node.attributes['label']}" + "}")
         self.body.append(latex_admonition_start)
+        self.no_latex_floats += 1
     else:
         self.body.append(self.starttag(node, "div", CLASS="admonition"))
 
@@ -43,6 +44,7 @@
         idx = list_rindex(self.body, latex_admonition_start) + 2
         self.body.insert(idx, f"{realtyp.title()} {number}")
         self.body.append(latex_admonition_end)
+        self.no_latex_floats -= 1
     else:
         # Find index in list of 'Proof #'
         number = get_node_number(self, node, countertyp)
@@ -56,6 +58,7 @@
         docname = find_parent(self.builder.env, node, "section")
         self.body.append("\\label{" + f"{docname}:{node.attributes['label']}" + "}")
         self.body.append(latex_admonition_start)
+        self.no_latex_floats += 1
     else:
         self.body.append(self.starttag(node, "div", CLASS="admonition"))
 
@@ -67,6 +70,7 @@
         idx = list_rindex(self.body, latex_admonition_start) + 2
         self.body.insert(idx, f"{realtyp.title()}")
         self.body.append(latex_admonition_end)
+        self.no_latex_floats -= 1
     else:
         # use the id to find the correct title location
         search_str = f'<p class="admonition-title" id="{id}">'

With this patch, all three figures in the reproduction are written as [H] and the PDF builds. The depart_* functions find the box with list_rindex(self.body, latex_admonition_start), so the visitors must keep appending that exact string; the patch leaves it unchanged.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions