From a4c724e9c56b19093b69908912fca97e7fb15ed4 Mon Sep 17 00:00:00 2001 From: Elijah Greenstein <197816462+elijahgreenstein@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:25:25 -0800 Subject: [PATCH 1/8] Add `numfig_reset` config option to Config class --- sphinx/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sphinx/config.py b/sphinx/config.py index f82e2b761ee..2c63b91ac3f 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -288,6 +288,7 @@ class Config: 'numfig_secnum_depth': _Opt(1, 'env', frozenset((int, types.NoneType))), # numfig_format will be initialized in init_numfig_format() 'numfig_format': _Opt({}, 'env', frozenset((dict,))), + 'numfig_reset': _Opt(False, 'env', frozenset((bool,))), 'maximum_signature_line_length': _Opt( None, 'env', frozenset((int, types.NoneType)) ), From dd4c1ba35ac461ca7b622ff8be4d534ba8edd72f Mon Sep 17 00:00:00 2001 From: Elijah Greenstein <197816462+elijahgreenstein@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:26:39 -0800 Subject: [PATCH 2/8] Reset figure numbering when `numfig_reset` is set Only applies to `numbered` toctrees. --- sphinx/environment/collectors/toctree.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py index 5c3d5c97f8c..a0532da4886 100644 --- a/sphinx/environment/collectors/toctree.py +++ b/sphinx/environment/collectors/toctree.py @@ -349,6 +349,8 @@ def _walk_doctree( else: _walk_doctree(docname, subnode, secnum) elif isinstance(subnode, addnodes.toctree): + if docname in env.numbered_toctrees and env.config.numfig_reset: + fignum_counter.clear() for _title, subdocname in subnode['entries']: if url_re.match(subdocname) or subdocname == 'self': # don't mess with those From bb01672d1fa686e2a24ffc9d08205dcfb58ceaee Mon Sep 17 00:00:00 2001 From: Elijah Greenstein <197816462+elijahgreenstein@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:31:52 -0800 Subject: [PATCH 3/8] Add info about `numfig_reset` option to config doc --- doc/usage/configuration.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index ff903fa4f6c..81e8cca6a3b 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -359,6 +359,25 @@ Options for figure numbering .. versionadded:: 1.3 + +.. confval:: numfig_reset + :type: :code-py:`bool` + :default: :code-py:`False` + + If :code-py:`True`, then Sphinx resets figure numbering every time Sphinx + encounters a :rst:dir:`toctree` directive with the ``:numbered:`` option + activated. Use this option to make HTML and/or EPUB figure numbers correspond + to LaTeX figure numbers when using :confval:`latex_documents` to group files + into multiple LaTeX documents. + + .. note:: + + This option does not change the figure numbers assigned by the LaTeX + builder. + + .. versionadded:: 8.4 + + .. confval:: numfig_secnum_depth :type: :code-py:`int` :default: :code-py:`1` From 488786166c0460f4b3bd4b67cf8c0adcd41ce266 Mon Sep 17 00:00:00 2001 From: Elijah Greenstein <197816462+elijahgreenstein@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:21:00 -0800 Subject: [PATCH 4/8] Change conf variable to `numfig_restart` --- doc/usage/configuration.rst | 12 ++++++------ sphinx/config.py | 2 +- sphinx/environment/collectors/toctree.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 81e8cca6a3b..23f5928145c 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -360,22 +360,22 @@ Options for figure numbering .. versionadded:: 1.3 -.. confval:: numfig_reset +.. confval:: numfig_restart :type: :code-py:`bool` :default: :code-py:`False` - If :code-py:`True`, then Sphinx resets figure numbering every time Sphinx + If :code-py:`True`, then Sphinx restarts figure numbering every time Sphinx encounters a :rst:dir:`toctree` directive with the ``:numbered:`` option - activated. Use this option to make HTML and/or EPUB figure numbers correspond - to LaTeX figure numbers when using :confval:`latex_documents` to group files - into multiple LaTeX documents. + activated. You can use this option to make HTML/EPUB figure numbers + correspond to LaTeX figure numbers when using :confval:`latex_documents` to + group files into multiple LaTeX documents. .. note:: This option does not change the figure numbers assigned by the LaTeX builder. - .. versionadded:: 8.4 + .. versionadded:: 8.3 .. confval:: numfig_secnum_depth diff --git a/sphinx/config.py b/sphinx/config.py index 2c63b91ac3f..28bc6c35c94 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -288,7 +288,7 @@ class Config: 'numfig_secnum_depth': _Opt(1, 'env', frozenset((int, types.NoneType))), # numfig_format will be initialized in init_numfig_format() 'numfig_format': _Opt({}, 'env', frozenset((dict,))), - 'numfig_reset': _Opt(False, 'env', frozenset((bool,))), + 'numfig_restart': _Opt(False, 'env', frozenset((bool,))), 'maximum_signature_line_length': _Opt( None, 'env', frozenset((int, types.NoneType)) ), diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py index a0532da4886..283dfeaa621 100644 --- a/sphinx/environment/collectors/toctree.py +++ b/sphinx/environment/collectors/toctree.py @@ -349,7 +349,7 @@ def _walk_doctree( else: _walk_doctree(docname, subnode, secnum) elif isinstance(subnode, addnodes.toctree): - if docname in env.numbered_toctrees and env.config.numfig_reset: + if docname in env.numbered_toctrees and env.config.numfig_restart: fignum_counter.clear() for _title, subdocname in subnode['entries']: if url_re.match(subdocname) or subdocname == 'self': From 7e2e6cffb70aa862492c900177f1c2c86193c748 Mon Sep 17 00:00:00 2001 From: Elijah Greenstein <197816462+elijahgreenstein@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:21:52 -0800 Subject: [PATCH 5/8] Add tests for `numfig_restart` --- tests/test_builders/test_build_html_numfig.py | 434 ++++++++++++++++++ 1 file changed, 434 insertions(+) diff --git a/tests/test_builders/test_build_html_numfig.py b/tests/test_builders/test_build_html_numfig.py index 144d9958d0d..a553e0fe718 100644 --- a/tests/test_builders/test_build_html_numfig.py +++ b/tests/test_builders/test_build_html_numfig.py @@ -1143,3 +1143,437 @@ def test_numfig_with_singlehtml( ) -> None: app.build() check_xpath(cached_etree_parse(app.outdir / 'index.html'), 'index.html', *expect) + + +@pytest.mark.parametrize( + ('fname', 'path', 'check', 'be_found'), + [ + ( + 'index.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1 $', + True, + ), + ( + 'index.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 2 $', + True, + ), + ( + 'index.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1 $', + True, + ), + ( + 'index.html', + ".//table/caption/span[@class='caption-number']", + '^Table 2 $', + True, + ), + ( + 'index.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1 $', + True, + ), + ( + 'index.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 2 $', + True, + ), + ('index.html', './/li/p/a/span', '^Fig. 1$', True), + ('index.html', './/li/p/a/span', '^Figure1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Table 1$', True), + ('index.html', './/li/p/a/span', '^Table:1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Listing 1$', True), + ('index.html', './/li/p/a/span', '^Code-1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Section.1$', True), + ('index.html', './/li/p/a/span', '^Section.1.1$', True), # bar.rst + ('index.html', './/li/p/a/span', '^Fig.1 should be Fig.1$', True), + ('index.html', './/li/p/a/span', '^Sect.1 Foo$', True), + ( + 'foo.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.1 $', + True, + ), + ( + 'foo.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.2 $', + True, + ), + ( + 'foo.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.3 $', + True, + ), + ( + 'foo.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.4 $', + True, + ), + ( + 'foo.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.1 $', + True, + ), + ( + 'foo.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.2 $', + True, + ), + ( + 'foo.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.3 $', + True, + ), + ( + 'foo.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.4 $', + True, + ), + ( + 'foo.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.1 $', + True, + ), + ( + 'foo.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.2 $', + True, + ), + ( + 'foo.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.3 $', + True, + ), + ( + 'foo.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.4 $', + True, + ), + ( + 'bar.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.1 $', + True, + ), + ( + 'bar.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.3 $', + True, + ), + ( + 'bar.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.4 $', + True, + ), + ( + 'bar.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.1 $', + True, + ), + ( + 'bar.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.3 $', + True, + ), + ( + 'bar.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.4 $', + True, + ), + ( + 'bar.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.1 $', + True, + ), + ( + 'bar.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.3 $', + True, + ), + ( + 'bar.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.4 $', + True, + ), + ( + 'baz.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.2 $', + True, + ), + ( + 'baz.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.2 $', + True, + ), + ( + 'baz.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.2 $', + True, + ), + ], +) +@pytest.mark.sphinx( + 'html', + testroot='numfig', + confoverrides={'numfig': True, 'numfig_restart': True}, +) +@pytest.mark.test_params(shared_result='test_build_html_numfig_restart') +def test_numfig_restart( + app: SphinxTestApp, + cached_etree_parse: Callable[[Path], ElementTree], + fname: str, + path: str, + check: str | None, + be_found: bool, +) -> None: + # Set up two numbered toctrees + index = (app.srcdir / 'index.rst').read_text(encoding='utf8') + index = index.replace(' foo', ' foo\n\n.. toctree::\n :numbered:\n') + (app.srcdir / 'index.rst').write_text(index, encoding='utf8') + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, path, check, be_found) + + +@pytest.mark.parametrize( + ('fname', 'path', 'check', 'be_found'), + [ + ( + 'index.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1 $', + True, + ), + ( + 'index.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 2 $', + True, + ), + ( + 'index.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1 $', + True, + ), + ( + 'index.html', + ".//table/caption/span[@class='caption-number']", + '^Table 2 $', + True, + ), + ( + 'index.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1 $', + True, + ), + ( + 'index.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 2 $', + True, + ), + ('index.html', './/li/p/a/span', '^Fig. 1$', True), + ('index.html', './/li/p/a/span', '^Figure1.1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Table 1$', True), + ('index.html', './/li/p/a/span', '^Table:1.1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Listing 1$', True), + ('index.html', './/li/p/a/span', '^Code-1.1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Section.1$', True), + ('index.html', './/li/p/a/span', '^Section.1.1$', True), # bar.rst + ('index.html', './/li/p/a/span', '^Fig.1 should be Fig.1$', True), + ('index.html', './/li/p/a/span', '^Sect.1 Foo$', True), + ( + 'foo.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.1 $', + True, + ), + ( + 'foo.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.1.1 $', + True, + ), + ( + 'foo.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.1.2 $', + True, + ), + ( + 'foo.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.2.1 $', + True, + ), + ( + 'foo.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.1 $', + True, + ), + ( + 'foo.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.1.1 $', + True, + ), + ( + 'foo.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.1.2 $', + True, + ), + ( + 'foo.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.2.1 $', + True, + ), + ( + 'foo.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.1 $', + True, + ), + ( + 'foo.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.1.1 $', + True, + ), + ( + 'foo.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.1.2 $', + True, + ), + ( + 'foo.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.2.1 $', + True, + ), + ( + 'bar.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.1.1 $', + True, + ), + ( + 'bar.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.1.3 $', + True, + ), + ( + 'bar.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.2.1 $', + True, + ), + ( + 'bar.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.1.1 $', + True, + ), + ( + 'bar.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.1.3 $', + True, + ), + ( + 'bar.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.2.1 $', + True, + ), + ( + 'bar.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.1.1 $', + True, + ), + ( + 'bar.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.1.3 $', + True, + ), + ( + 'bar.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.2.1 $', + True, + ), + ( + 'baz.html', + FIGURE_CAPTION + "/span[@class='caption-number']", + '^Fig. 1.1.2 $', + True, + ), + ( + 'baz.html', + ".//table/caption/span[@class='caption-number']", + '^Table 1.1.2 $', + True, + ), + ( + 'baz.html', + ".//div[@class='code-block-caption']/span[@class='caption-number']", + '^Listing 1.1.2 $', + True, + ), + ], +) +@pytest.mark.sphinx( + 'html', + testroot='numfig', + confoverrides={'numfig': True, 'numfig_restart': True, 'numfig_secnum_depth': 2}, +) +@pytest.mark.test_params(shared_result='test_build_html_numfig_restart_secnum_depth_2') +def test_numfig_restart_secnum_depth_2( + app: SphinxTestApp, + cached_etree_parse: Callable[[Path], ElementTree], + fname: str, + path: str, + check: str | None, + be_found: bool, +) -> None: + # Set up two numbered toctrees + index = (app.srcdir / 'index.rst').read_text(encoding='utf8') + index = index.replace(' foo', ' foo\n\n.. toctree::\n :numbered:\n') + (app.srcdir / 'index.rst').write_text(index, encoding='utf8') + app.build() + check_xpath(cached_etree_parse(app.outdir / fname), fname, path, check, be_found) From 5ed724ccaafcb8df98af88068dd3f7fe25ff53d4 Mon Sep 17 00:00:00 2001 From: Elijah Greenstein <197816462+elijahgreenstein@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:27:45 -0800 Subject: [PATCH 6/8] Add feature to `CHANGES.rst` --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index cd36d83957b..56e22171fd8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -68,6 +68,8 @@ Features added Patch by Jean-François B. * #13508: Initial support for :pep:`695` type aliases. Patch by Martin Matouš, Jeremy Maitin-Shepard, and Adam Turner. +* Add :confval:`numfig_restart` option to make it possible to restart figure + numbering at each ``:numbered:`` toctree. Patch by Elijah Greenstein. Bugs fixed ---------- From dfda2d3b89e2588f3f4ece712a359327a5cf8c6c Mon Sep 17 00:00:00 2001 From: Elijah Greenstein <197816462+elijahgreenstein@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:36:56 -0800 Subject: [PATCH 7/8] Add name to AUTHORS; add PR number to CHANGES --- AUTHORS.rst | 1 + CHANGES.rst | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 707c77aec04..098412b80f7 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -50,6 +50,7 @@ Contributors * Dimitri Papadopoulos Orfanos -- linting and spelling * Dmitry Shachnev -- modernisation and reproducibility * Doug Hellmann -- graphviz improvements +* Elijah Greenstein -- numfig_restart option * Eric Larson -- better error messages * Eric N. Vander Weele -- autodoc improvements * Eric Wieser -- autodoc improvements diff --git a/CHANGES.rst b/CHANGES.rst index 56e22171fd8..9d040f379c0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -68,8 +68,8 @@ Features added Patch by Jean-François B. * #13508: Initial support for :pep:`695` type aliases. Patch by Martin Matouš, Jeremy Maitin-Shepard, and Adam Turner. -* Add :confval:`numfig_restart` option to make it possible to restart figure - numbering at each ``:numbered:`` toctree. Patch by Elijah Greenstein. +* #14081: Add :confval:`numfig_restart` option to make it possible to restart + figure numbering at each ``:numbered:`` toctree. Patch by Elijah Greenstein. Bugs fixed ---------- From 3da0dee503246097a8c907d0608efda5bc75b7f1 Mon Sep 17 00:00:00 2001 From: Elijah Greenstein <197816462+elijahgreenstein@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:02:55 -0800 Subject: [PATCH 8/8] Reformat comments to satisfy ruff --- tests/test_builders/test_build_html_numfig.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_builders/test_build_html_numfig.py b/tests/test_builders/test_build_html_numfig.py index a553e0fe718..7e5171ffcd9 100644 --- a/tests/test_builders/test_build_html_numfig.py +++ b/tests/test_builders/test_build_html_numfig.py @@ -1185,13 +1185,13 @@ def test_numfig_with_singlehtml( True, ), ('index.html', './/li/p/a/span', '^Fig. 1$', True), - ('index.html', './/li/p/a/span', '^Figure1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Figure1.2$', True), # baz.rst ('index.html', './/li/p/a/span', '^Table 1$', True), - ('index.html', './/li/p/a/span', '^Table:1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Table:1.2$', True), # baz.rst ('index.html', './/li/p/a/span', '^Listing 1$', True), - ('index.html', './/li/p/a/span', '^Code-1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Code-1.2$', True), # baz.rst ('index.html', './/li/p/a/span', '^Section.1$', True), - ('index.html', './/li/p/a/span', '^Section.1.1$', True), # bar.rst + ('index.html', './/li/p/a/span', '^Section.1.1$', True), # bar.rst ('index.html', './/li/p/a/span', '^Fig.1 should be Fig.1$', True), ('index.html', './/li/p/a/span', '^Sect.1 Foo$', True), ( @@ -1402,13 +1402,13 @@ def test_numfig_restart( True, ), ('index.html', './/li/p/a/span', '^Fig. 1$', True), - ('index.html', './/li/p/a/span', '^Figure1.1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Figure1.1.2$', True), # baz.rst ('index.html', './/li/p/a/span', '^Table 1$', True), - ('index.html', './/li/p/a/span', '^Table:1.1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Table:1.1.2$', True), # baz.rst ('index.html', './/li/p/a/span', '^Listing 1$', True), - ('index.html', './/li/p/a/span', '^Code-1.1.2$', True), # baz.rst + ('index.html', './/li/p/a/span', '^Code-1.1.2$', True), # baz.rst ('index.html', './/li/p/a/span', '^Section.1$', True), - ('index.html', './/li/p/a/span', '^Section.1.1$', True), # bar.rst + ('index.html', './/li/p/a/span', '^Section.1.1$', True), # bar.rst ('index.html', './/li/p/a/span', '^Fig.1 should be Fig.1$', True), ('index.html', './/li/p/a/span', '^Sect.1 Foo$', True), (