Repository navigation
FIX: Keep figures inside exercise and solution boxes from floating in LaTeX - #95
Conversation
… LaTeX
Sphinx's LaTeX writer increments no_latex_floats when it opens an
admonition, so visit_figure writes \begin{figure}[H] rather than a
float. The exercise and solution visitors open the same sphinxadmonition
box but never touched the counter, so a captioned figure inside them was
written as \begin{figure}[htbp] and LaTeX stopped with "Not in outer par
mode".
Open and close the box through a helper pair that also maintains the
counter, used by all three LaTeX visit/depart pairs (exercise_node,
exercise_enumerable_node and solution_node; gated blocks are transformed
into these nodes).
Add a latexfigure test root with a figure in each kind of box, plus one
after them that must still float, so the counter is shown to be restored.
Fixes #94
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Note
Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.
🟢 Approval recommended
The change is narrowly focused on correcting LaTeX figure handling in exercise/solution boxes and is backed by a new dedicated LaTeX regression test that inspects generated output to confirm [H] is used inside boxed environments and normal float behavior is restored afterward.
No open issues 🎉
What changed in this PR
This pull request fixes LaTeX/PDF builds failing with LaTeX Error: Not in outer par mode when captioned figures appear inside exercise / solution boxes (including gated *-start / *-end blocks). It aligns the extension’s LaTeX output with Sphinx’s admonition behavior by ensuring figures inside these boxed environments are emitted as non-floating [H] figures, while figures outside remain normal floats.
Changes:
- Add shared LaTeX admonition enter/exit helpers and update
no_latex_floatsbookkeeping so in-box figures use[H]. - Add a dedicated LaTeX test root plus a regression test asserting
[H]inside boxes and[htbp]after boxes (verifying the counter is restored). - Document the fix under
[Unreleased]in the changelog.
| File | Summary |
|---|---|
tests/test_latex.py |
Adds a regression test that inspects generated LaTeX to verify figure placement is [H] inside exercise/solution boxes and [htbp] outside. |
tests/books/test-latexfigure/index.rst |
New test content covering numbered/unnumbered exercises, solutions, and gated blocks containing captioned figures, plus a figure after the boxes. |
tests/books/test-latexfigure/conf.py |
Minimal Sphinx configuration for the new LaTeX regression test root. |
sphinx_exercise/nodes.py |
Centralizes LaTeX admonition open/close logic and increments/decrements no_latex_floats so figures inside exercise/solution boxes render as [H]. |
CHANGELOG.md |
Adds an [Unreleased] entry describing the LaTeX figure-in-admonition fix. |
This pull request is too complex or risky for automated approval.
Review effort: Balanced
Add a notebook to the latexfigure test root with a code-cell whose
mystnb figure metadata puts a captioned figure inside a gated solution,
which is how Jupyter Book projects usually hit the float bug. Its
outputs are stored and nb_execution_mode is "off", so no kernel runs.
Match figures by their :name: label instead of their caption. The
caption regex stopped at the first closing brace, so a caption with
markup such as \sphinxhyphen{} was misread, and a figure without a
caption was paired with the next one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions (#1121) sphinx-exercise 1.2.2 writes a captioned figure inside an exercise or solution box as `[H]`, as Sphinx does inside its own admonitions, so the PDF build no longer stops with "Not in outer par mode" (executablebooks/sphinx-exercise#94, executablebooks/sphinx-exercise#95). - `environment.yml` and `.github/workflows/collab.yml`: sphinx-exercise 1.2.1 → 1.2.2. - Restores the caption and name on nine figures in gated solutions, removed as the workaround in #1101, #1110 and #1119: exchange_rate_learning (2), genetic_classifier (1), learning_approximation (2), lucas_prescott_search (3), information_transfer (1). Part of QuantEcon/workspace-lectures#70. The workaround is recorded on QuantEcon/meta#297. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* FIX: Keep figures inside prf boxes from floating in LaTeX
A captioned figure inside a prf: directive box, numbered or :nonumber:,
broke the LaTeX/PDF build with "LaTeX Error: Not in outer par mode".
Sphinx's LaTeX writer increments no_latex_floats whenever it opens one
of its own admonitions, and visit_figure then writes
\begin{figure}[H]. The enumerable and unenumerable visitors open the
same sphinxadmonition box but never touched the counter, so figures
inside it were written as floating \begin{figure}[htbp], which LaTeX
rejects there.
Open and close the box through a small visit_latex_admonition /
depart_latex_admonition helper pair that also increments and
decrements the counter. prf:proof was already unaffected: it wraps a
docutils admonition node that Sphinx's own visitor handles. HTML output
is unchanged.
Add the tests/books/test-latexfigure root and
tests/test_latex.py::test_latex_figure_in_admonition, and a CHANGELOG
entry. Mirrors the change in executablebooks/sphinx-exercise#95.
Fixes #169
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WT8bMZoSvoQTbzkdmZiMnd
* TST: Fail on duplicate figure names in latex_figures
latex_figures keyed figures by name, so a repeated name, or a second
unnamed figure, silently replaced an earlier entry and dropped it from
the comparison. Assert that each name is new.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WT8bMZoSvoQTbzkdmZiMnd
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #94
A captioned figure inside an
exerciseorsolutionbox, including gatedexercise-start/solution-startblocks, breaks the LaTeX/PDF build withLaTeX Error: Not in outer par mode. Sphinx's LaTeX writer incrementsno_latex_floatswhenever it opens one of its own admonitions, andvisit_figurethen writes\begin{figure}[H]instead of a float. sphinx-exercise opens the samesphinxadmonitionbox but never touched the counter, so figures inside it were written as\begin{figure}[htbp], which LaTeX rejects inside the box.Changes
sphinx_exercise/nodes.py: the three LaTeX visit/depart pairs (exercise_node,exercise_enumerable_node,solution_node) now open and close the box through a smallvisit_latex_admonition/depart_latex_admonitionhelper pair that also increments and decrementsself.no_latex_floats. Gated blocks are transformed into these nodes, so they are covered too. HTML output is unchanged.tests/books/test-latexfigure/andtests/test_latex.py::test_latex_figure_in_admonition: a figure in a numbered exercise, an unnumbered exercise, a solution, a gated exercise and a gated solution, plus a notebook whosecode-cellhasmystnb: figure:metadata inside a gated solution, which is how Jupyter Book projects usually hit this. Each is asserted to be[H]inside its box, and a figure after the boxes must still be[htbp], which shows the counter is restored on depart. The notebook stores its outputs and the root setsnb_execution_mode = "off", so no kernel runs. Figures are matched by their:name:label rather than by caption text, so captions with LaTeX markup are handled. The existing file_regression fixtures are untouched.CHANGELOG.md: entry under[Unreleased].Testing
main(every in-box figure comes out as[htbp]) with Sphinx 6.2.1, 7.4.7, 8.1.3, 8.2.3 and 9.1.1, and passes with this change on all of them. CI does not test Sphinx 9, but the package allows it at runtime, and it usesno_latex_floatsthe same way.latexmk -pdfon Sphinx 7.4.7: the new test root, including the notebook figure, stops with "Not in outer par mode" onmainand compiles with this change.See QuantEcon/meta#297
🤖 Generated with Claude Code