Skip to content

FIX: Keep figures inside exercise and solution boxes from floating in LaTeX - #95

Merged
mmcky merged 3 commits into
mainfrom
fix/latex-figure-float
Nov 10, 2026
Merged

mmcky merged 3 commits into
mainfrom
fix/latex-figure-float

Conversation

@mmcky

@mmcky mmcky commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Fixes #94

A captioned figure inside an exercise or solution box, including gated exercise-start / solution-start blocks, breaks 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] instead of a float. sphinx-exercise opens the same sphinxadmonition box 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 small visit_latex_admonition / depart_latex_admonition helper pair that also increments and decrements self.no_latex_floats. Gated blocks are transformed into these nodes, so they are covered too. HTML output is unchanged.
  • New test root tests/books/test-latexfigure/ and tests/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 whose code-cell has mystnb: 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 sets nb_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

  • The new test fails on 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 uses no_latex_floats the same way.
  • Full suite, installed the way CI installs it: 117 passed on each of the 11 matrix combinations (Python 3.10, 3.11 and 3.12 with Sphinx 6, 7 and 8; Python 3.13 with Sphinx 7 and 8). pre-commit passes.
  • End to end with latexmk -pdf on Sphinx 7.4.7: the new test root, including the notebook figure, stops with "Not in outer par mode" on main and compiles with this change.

See QuantEcon/meta#297

🤖 Generated with Claude Code

… 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>
Copilot AI balanced review requested due to automatic review settings November 10, 2026 03:05
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_floats bookkeeping 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LaTeX: figures inside exercise and solution boxes are written as floats ("Not in outer par mode")

2 participants