Repository navigation
Description
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 lemmaRunning 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