diff --git a/make_docs.sh b/make_docs.sh
index 67a2a02..6bda9d9 100755
--- a/make_docs.sh
+++ b/make_docs.sh
@@ -5,7 +5,15 @@ set -Eeuo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT"
-mkdocs build --clean
+DOCS_TMP="$(mktemp -d)"
+cleanup() {
+ rm -rf "$DOCS_TMP"
+}
+trap cleanup EXIT
+
+python3 "$ROOT/scripts/prepare_docs_sources.py" "$ROOT/meanas" "$DOCS_TMP"
+
+MKDOCSTRINGS_PYTHON_PATH="$DOCS_TMP" mkdocs build --clean
PRINT_PAGE='site/print_page/index.html'
if [[ -f "$PRINT_PAGE" ]] && command -v htmlark >/dev/null 2>&1; then
diff --git a/meanas/fdtd/energy.py b/meanas/fdtd/energy.py
index 6df30dc..57387aa 100644
--- a/meanas/fdtd/energy.py
+++ b/meanas/fdtd/energy.py
@@ -57,6 +57,7 @@ def poynting(
(see `meanas.tests.test_fdtd.test_poynting_planes`)
The full relationship is
+
$$
\begin{aligned}
(U_{l+\frac{1}{2}} - U_l) / \Delta_t
diff --git a/mkdocs.yml b/mkdocs.yml
index 837284c..a0dcd2c 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -34,7 +34,7 @@ plugins:
handlers:
python:
paths:
- - .
+ - !ENV [MKDOCSTRINGS_PYTHON_PATH, "."]
options:
show_root_heading: true
show_root_toc_entry: false
diff --git a/scripts/prepare_docs_sources.py b/scripts/prepare_docs_sources.py
new file mode 100644
index 0000000..7e9bcc5
--- /dev/null
+++ b/scripts/prepare_docs_sources.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+"""Prepare a temporary source tree for docs generation.
+
+The live source keeps readable display-math blocks written as standalone
+`$$ ... $$` docstring sections. MkDocs + mkdocstrings does not consistently
+preserve those blocks as MathJax input when they appear inside API docstrings,
+so the docs build rewrites them in a temporary copy into explicit
+`
...
` containers.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+import shutil
+import sys
+
+
+def _rewrite_display_math(text: str) -> str:
+ lines = text.splitlines(keepends=True)
+ output: list[str] = []
+ in_block = False
+ block_indent = ""
+
+ for line in lines:
+ stripped = line.strip()
+ indent = line[: len(line) - len(line.lstrip())]
+
+ if not in_block:
+ if stripped == "$$":
+ block_indent = indent
+ output.append(f'{block_indent}\\[\n')
+ in_block = True
+ continue
+
+ if stripped.startswith("$$") and stripped.endswith("$$") and stripped != "$$":
+ body = stripped[2:-2].strip()
+ output.append(f'{indent}
\\[ {body} \\]
\n')
+ continue
+
+ if stripped.startswith("$$"):
+ block_indent = indent
+ body = stripped[2:].strip()
+ output.append(f'{block_indent}
\\[\n')
+ if body:
+ output.append(f"{block_indent}{body}\n")
+ in_block = True
+ continue
+
+ output.append(line)
+ continue
+
+ if stripped == "$$":
+ output.append(f"{block_indent}\\]
\n")
+ in_block = False
+ block_indent = ""
+ continue
+
+ if stripped.endswith("$$"):
+ body = stripped[:-2].rstrip()
+ if body:
+ output.append(f"{block_indent}{body}\n")
+ output.append(f"{block_indent}\\]
\n")
+ in_block = False
+ block_indent = ""
+ continue
+
+ output.append(line)
+
+ if in_block:
+ raise ValueError("unterminated display-math block")
+
+ return "".join(output)
+
+
+def main() -> int:
+ if len(sys.argv) != 3:
+ print("usage: prepare_docs_sources.py ", file=sys.stderr)
+ return 2
+
+ src_dir = Path(sys.argv[1]).resolve()
+ dst_root = Path(sys.argv[2]).resolve()
+ dst_pkg = dst_root / src_dir.name
+
+ shutil.copytree(src_dir, dst_pkg)
+
+ for path in dst_pkg.rglob("*.py"):
+ path.write_text(_rewrite_display_math(path.read_text()))
+
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())