90 lines
2.8 KiB
Python
90 lines
2.8 KiB
Python
"""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
|