A clean book theme for scientific explanations and documentation with Sphinx
Post-processing transforms that modify document structure after parsing. The primary transform converts footnotes to margin/side notes for enhanced book-style presentation.
The main transform class that converts standard footnotes into interactive margin notes.
class HandleFootnoteTransform(SphinxPostTransform):
"""
Transform footnotes into side/margin notes for book-style presentation.
Converts standard reStructuredText footnotes into interactive
margin content that appears beside the main text on desktop
and below on mobile devices.
"""
default_priority: int = 1 # Transform priority
formats: tuple = ("html",) # Only applies to HTML output
def run(self, **kwargs: Any) -> None:
"""
Execute the footnote transformation.
Process:
1. Check if sidenotes are enabled in theme options
2. Find all footnote references in the document
3. Locate corresponding footnote definitions
4. Convert footnotes to SideNoteNode instances
5. Handle marginnotes (starting with {-}) differently from sidenotes
6. Position notes appropriately in document structure
7. Remove original footnote definitions
Parameters:
- kwargs: Additional keyword arguments from Sphinx
"""Utility functions for cross-version compatibility with docutils.
def findall(node: Element, *args, **kwargs) -> Iterator[Element]:
"""
Compatibility wrapper for docutils node traversal.
Uses findall() in newer docutils versions, falls back to traverse()
in older versions.
Parameters:
- node: Document node to search
- args: Arguments passed to findall/traverse
- kwargs: Keyword arguments passed to findall/traverse
Returns:
Iterator over matching nodes
"""# In Sphinx conf.py
html_theme_options = {
"use_sidenotes": True # Enable footnote to sidenote conversion
}In reStructuredText source:
This is regular text with a footnote reference [#note1]_.
This is text with a margin note [#margin1]_.
.. [#note1] This will become a sidenote with a number.
.. [#margin1] {-} This will become a marginnote without a number.from sphinx.application import Sphinx
from sphinx_book_theme._transforms import HandleFootnoteTransform
def setup(app: Sphinx):
# Register the post-transform
app.add_post_transform(HandleFootnoteTransform)
return {"parallel_read_safe": True}from sphinx_book_theme._transforms import HandleFootnoteTransform
from sphinx_book_theme._compat import findall
from docutils import nodes as docutil_nodes
class CustomFootnoteTransform(HandleFootnoteTransform):
"""Custom footnote transform with additional processing."""
def run(self, **kwargs):
# Get theme options
theme_options = get_theme_options_dict(self.app)
if not theme_options.get("use_sidenotes", False):
return None
# Use compatibility function for node traversal
for ref_node in findall(self.document, docutil_nodes.footnote_reference):
# Custom processing logic here
passfrom sphinx_book_theme._compat import findall
from docutils import nodes
# Find all emphasis nodes in a document
for emphasis in findall(document, nodes.emphasis):
print(f"Emphasized text: {emphasis.astext()}")
# Find nodes with specific attributes
for node in findall(document, nodes.paragraph):
if 'highlight' in node.attributes.get('classes', []):
print(f"Highlighted paragraph: {node.astext()}")[#note1]_.. [#note1] content{-}, no number shown<label for='sidenote-role-1' class='margin-toggle'>
<span class='sidenote'>
<sup>1</sup>
Sidenote content here
</span>
</label>
<input type='checkbox' id='sidenote-role-1' class='margin-toggle'><label for='marginnote-role-1' class='margin-toggle marginnote-label'>
</label>
<input type='checkbox' id='marginnote-role-1' class='margin-toggle'># Theme options affecting transform behavior
html_theme_options = {
"use_sidenotes": True, # Enable/disable the transform
}Install with Tessl CLI
npx tessl i tessl/pypi-sphinx-book-theme