rbpu_datasheet/doc_builder/renderers/schemdraw.py

90 lines
2.8 KiB
Python
Raw Permalink Normal View History

2026-07-19 20:56:15 +08:00
"""Schemdraw code block renderer.
Executes Python schemdraw code from a chapter code block and returns SVG.
Usage in markdown:
```schemdraw
import schemdraw
from schemdraw import elements as e
with schemdraw.Drawing(show=False) as d:
d += e.Resistor().right().label('R1')
```
"""
import base64
import subprocess
import sys
import tempfile
import textwrap
from pathlib import Path
def render(code: str, project_dir: Path = None) -> str:
"""Execute schemdraw code, return inline SVG as HTML <img> tag.
Args:
code: Python source code using schemdraw
project_dir: project root (unused, kept for interface consistency)
Returns:
HTML string with inline base64 SVG image
"""
# Write code to a temp script and execute, capturing SVG output
wrapped = textwrap.dedent(code).strip()
with tempfile.NamedTemporaryFile(
suffix='.svg', mode='w+', encoding='utf-8', delete=False
) as tmp:
svg_path = Path(tmp.name)
try:
# Execute the schemdraw code as a subprocess for isolation.
# The script must produce 'OUTPUT: <svg filename>' on the last line,
# or we inject a save hook.
script = _wrap_code(wrapped, str(svg_path))
result = subprocess.run(
[sys.executable, '-c', script],
capture_output=True, text=True, timeout=30,
)
if result.returncode != 0:
return (
f'<div class="warning">'
f'Schemdraw error:<pre>{result.stderr[:500] or result.stdout[:500]}</pre>'
f'</div>'
)
# Read the generated SVG
if svg_path.exists() and svg_path.stat().st_size > 0:
svg_content = svg_path.read_text(encoding='utf-8')
b64 = base64.b64encode(svg_content.encode('utf-8')).decode('ascii')
return (
f'<figure class="img-sm">'
f'<img src="data:image/svg+xml;base64,{b64}"'
f' alt="schemdraw diagram">'
f'</figure>'
)
else:
return (
f'<div class="warning">'
f'Schemdraw ran but produced no SVG output.'
f'</div>'
)
finally:
if svg_path.exists():
svg_path.unlink(missing_ok=True)
def _wrap_code(code: str, svg_path: str) -> str:
"""Wrap user code to redirect schemdraw output to a file."""
# schemdraw writes SVG to the 'file' parameter of Drawing().
# We modify the code to inject file= if missing.
if "file=" not in code and "Drawing(" in code:
code = code.replace(
'Drawing(show=False)',
f"Drawing(show=False, file=r'{svg_path}')",
)
code = code.replace(
'Drawing()',
f"Drawing(show=False, file=r'{svg_path}')",
)
return code