Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 64 additions & 15 deletions doc/_ext/latex_tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,32 @@
labels render as unstyled text with no visual separation between variants.

This extension inserts a ``SphinxTransform`` (LaTeX/rinoh builds only) that
rewrites the generic container tree produced by sphinx-tabs into custom
``LatexTabsGroup`` / ``LatexTabEntry`` nodes, and registers LaTeX visitors
that render each tab as an unnumbered subsection heading followed by its
content.
rewrites the sphinx-tabs tree into custom ``LatexTabsGroup`` / ``LatexTabEntry``
nodes, and registers LaTeX visitors that render each tab as an unnumbered
subsubsection heading followed by its content.

The transform handles two possible document-tree structures:

* **Non-HTML fallback** — produced when the LaTeX builder reads fresh RST.
sphinx-tabs emits plain ``nodes.container`` outer_nodes, each holding a tab
label container and a content container::

nodes.container[sphinx-tabs]
nodes.container ← outer_node (one per tab)
nodes.container ← tab (holds label)
nodes.container ← panel (holds content)

* **HTML-cached doctrees** — produced when a previous ``make html`` run has
already written ``.doctrees/`` files. The Makefile ``-M`` flag shares a
single ``_build/.doctrees/`` directory across all builders. Those cached
trees contain sphinx-tabs HTML node types::

nodes.container[sphinx-tabs]
SphinxTabsTablist ← children are SphinxTabsTab paragraph nodes
SphinxTabsPanel ← one per tab (content)
SphinxTabsPanel

Expected result in PDF::

Expand Down Expand Up @@ -54,6 +76,22 @@ def _escape_latex(text: str) -> str:
return _LATEX_SPECIAL.sub(lambda m: "\\" + m.group(1), text)


def _is_html_cached_structure(children: list) -> bool:
"""Return True if children look like an HTML-cached sphinx-tabs doctree.

The HTML doctree structure has a tablist container as first child, whose
children are sphinx-tabs tab nodes carrying class ``sphinx-tabs-tab``.
The non-HTML fallback has plain outer_node containers instead.
"""
if not children:
return False
first = children[0]
return isinstance(first, nodes.Element) and any(
isinstance(c, nodes.Element) and "sphinx-tabs-tab" in c.get("classes", [])
for c in first.children
)


class LatexTabsTransform(SphinxTransform):
"""Convert sphinx-tabs containers into styled LatexTabsGroup nodes.

Expand All @@ -78,24 +116,35 @@ class LatexTabsTransform(SphinxTransform):
default_priority = 500

def apply(self, **kwargs) -> None:
if self.app.builder.name not in ("latex", "rinoh"):
if self.env.app.builder.name not in ("latex", "rinoh"):
return

for tabs_node in self.document.traverse(
lambda n: isinstance(n, nodes.container)
and "sphinx-tabs" in n.get("classes", [])
):
group = LatexTabsGroup()
for outer in list(tabs_node.children):
if not isinstance(outer, nodes.container) or len(outer.children) < 2:
continue
tab_container = outer.children[0]
panel = outer.children[1]

entry = LatexTabEntry()
entry["label"] = tab_container.astext().strip()
entry += list(panel.children)
group += entry
children = list(tabs_node.children)

if _is_html_cached_structure(children):
# HTML-cached doctree: tablist of SphinxTabsTab nodes + SphinxTabsPanel nodes.
tablist, panels = children[0], children[1:]
for tab_node, panel in zip(tablist.children, panels):
entry = LatexTabEntry()
entry["label"] = tab_node.astext().strip()
entry += list(panel.children)
group += entry
else:
# Non-HTML fallback: plain container outer_nodes with [tab, panel] children.
for outer in children:
if not isinstance(outer, nodes.container) or len(outer.children) < 2:
continue
tab_container = outer.children[0]
panel = outer.children[1]
entry = LatexTabEntry()
entry["label"] = tab_container.astext().strip()
entry += list(panel.children)
group += entry

tabs_node.replace_self(group)

Expand Down
Loading