How it works¶
To turn :numref:`fig1` into Fig. 1, Sphinx needs three things about the
target: what kind of numbered object it is, which node ID its number is
recorded under, and its caption. It gets the first two by loading the document
that holds the target:
target_node = env.get_doctree(docname).ids.get(labelid)
figtype = self.get_enumerable_node_type(target_node)
fignumber = self.get_fignumber(env, builder, figtype, docname, target_node)
env.get_doctree() keeps the pickled bytes of each document, but unpickles a
fresh copy every time it is called, because callers may modify the tree they
receive. Resolving N references into documents whose trees are O(N) therefore
unpickles O(N) trees of size O(N).
What the extension does instead¶
Both facts are available while the document is being read, when the nodes are
already in memory. The extension collects them at the doctree-read event
and stores them in the build environment, keyed by document and node ID:
("index", "fig1"): ("figure", "id1")
References are then resolved from that record plus env.toc_fignumbers and
env.toc_secnumbers, which the environment already holds. No doctree is
loaded.
The label ID is not the numbering ID¶
A label and the ID a number is recorded under are usually different. Writing
.. _fig1:
.. figure:: diagram.png
A caption
gives the figure node the IDs ["id1", "fig1"], and Sphinx records its
number under the first one:
env.toc_fignumbers["index"]["figure"]["id1"] == (1,)
The label fig1 is therefore not enough to look up the number, which is why
the extension records both the figure type and the numbering ID. Sections
behave the same way: .. _index: above a title leaves the section’s own
generated ID first.
Reusing Sphinx’s resolver¶
The extension subclasses StandardDomain and registers it with
app.add_domain(..., override=True). Rather than reimplementing reference
resolution, it hands Sphinx’s unchanged resolver a stand-in for the target
document, whose one lookup returns a small node carrying the collected figure
type and numbering ID.
Every warning, numfig_format branch, and number lookup therefore still
runs in Sphinx. That is why output is identical, and why the extension is not
tied to the internals of one Sphinx release.
Labels the extension did not collect¶
A label can be registered without a document being read, for instance for a domain index page. Those references fall through to Sphinx’s normal path, including its doctree read, so behaviour is preserved for extensions that register labels themselves.
Upstream¶
The same approach, written directly into StandardDomain, is what the fix
for sphinx-doc/sphinx#12611 proposes: record the
figure type and numbering ID as each label is noted while reading, and resolve
from that. This extension exists so the change can be exercised on real
projects before it is released.