mmdc — Mermaid Diagram Converter for Python
Convert Mermaid diagrams to SVG, PNG, and PDF — fully offline and fast, just pip install mmdc.
No Node.js. No npm. No Chrome. No system packages. Mermaid v11 runs inside a small embedded JS engine (QuickJS-ng), and every raster/PDF conversion goes through resvg — no Pillow, no Cairo, nothing to compile.
pip install mmdc
Why mmdc?
The official Mermaid CLI (@mermaid-js/mermaid-cli) drives a real headless Chrome via Puppeteer. That works, but it's slow to start, heavy to install (~170MB+ of Chromium), and awkward to embed in a pipeline.
mmdc renders the actual, current Mermaid v11 JS library — not a reimplementation, not a subset — inside QuickJS-ng, a ~7MB embedded JS engine. Real text metrics (which a fake DOM can't fabricate on its own) come from a bundled font read directly via a small pure-Python TTF parser, and that same font is handed to resvg for final rendering — so layout and paint always agree, by construction.
Quick Start
import mmdc
d = mmdc.render("""
graph TD
A[Install] --> B[Import]
B --> C[Convert]
C --> D[Done]
""")
d.save("diagram.svg")
d.save("diagram.png", scale=2.0)
d.save("diagram.pdf", pdf_format="A4")
mmdc -i diagram.mermaid -o diagram.svg
mmdc -i diagram.mermaid -o diagram.png --scale 2.0
cat diagram.mermaid | mmdc -i - -o diagram.pdf
How It Works
flowchart LR
A[Mermaid source] --> B[QuickJS-ng]
B -->|"mermaid.js v11 (bundled)"| C[SVG]
C --> D[resvg]
D --> E[PNG]
C --> F["hand-written PDF writer<br/>(stdlib only)"]
F --> G[PDF]
H[bundled DejaVu Sans] -.font metrics.-> B
H -.same font, forced.-> D
Everything happens in one process, no subprocess, no I/O:
- SVG — mermaid.js runs inside QuickJS-ng against a minimal fake DOM/SVG implementation. The one thing a fake DOM can't fabricate — real text metrics (
getBBox/getComputedTextLength) — is bridged back into Python, which reads real glyph widths from a bundled font. - PNG — the SVG is rasterized by resvg, forced to use that same bundled font, so what mermaid measured during layout is exactly what gets painted.
- PDF — a small hand-written PDF writer (stdlib
zlib/structonly) embeds the rendered pixels directly. No Pillow, no Cairo, no reportlab — every mainstream "put an image in a PDF" library pulls in Pillow as a transitive dependency; this avoids that entirely.
Rendering is CPU-bound, synchronous, single-process — there's no browser or subprocess to wait on, so there's nothing for async to usefully overlap. See mmdc.render_many() below for real parallelism instead.
Python API
render(source, backend=None, **opts) -> Diagram
import mmdc
d = mmdc.render("flowchart LR; A-->B-->C") # SVG is rendered immediately
Diagram methods — SVG is already computed; everything else is derived from it on demand:
| Method | Returns | Notes |
|---|---|---|
.svg() |
str |
Already computed at render() time |
.png(width?, height?, scale?, background?) |
bytes |
Aspect ratio always preserved |
.pdf(pdf_format?, pdf_landscape?, pdf_margin?, width?, height?, scale?, background?) |
bytes |
pdf_format=None (default) fits the page to the diagram |
.raw(width?, height?, background?) |
(bytes, w, h) |
Raw RGBA8888, no imaging library involved |
.numpy(width?, height?, background?) |
np.ndarray |
(H, W, 4) uint8; requires numpy |
.save(path, ...) |
None |
Format inferred from the extension: .svg / .png / .pdf |
._repr_svg_() |
str |
Automatic inline rendering in Jupyter/IPython |
d.png(width=1200, background="#ffffff")
d.raw() # (bytes, width, height) -- RGBA8888
d.numpy() # np.ndarray, no Pillow needed
d.save("out.pdf", pdf_format="A4", pdf_margin="1cm")
Themes, config, CSS
mmdc.render(source, theme="dark") # "default" | "forest" | "dark" | "neutral"
mmdc.render(source, config={"flowchart": {"curve": "basis"}})
mmdc.render(source, css=".node rect { rx: 8; ry: 8; }")
Parallel batch rendering
Rendering is pure CPU work — no I/O to overlap, so real concurrency means real processes, not async:
diagrams = mmdc.render_many(sources, workers=4, theme="dark")
for d, name in zip(diagrams, output_names):
d.save(name)
Each worker process starts its own persistent engine once and reuses it for every diagram routed to it.
ASCII / terminal output (optional)
pip install mmdc[ascii]
print(mmdc.render_ascii("graph LR; A-->B-->C"))
┌───┐ ┌───┐ ┌───┐
│ A ├───►│ B ├───►│ C │
└───┘ └───┘ └───┘
Backed by termaid — pure Python, zero dependencies.
Low-level utilities
Rasterize any SVG string directly, without going through render():
from mmdc import svg_to_png, svg_to_raw
svg = open("diagram.svg").read()
png = svg_to_png(svg, width=1200, background="#ffffff")
raw, w, h = svg_to_raw(svg)
Additional backends (optional)
pip install mmdc[rust]
If mmdr (a native-Rust Mermaid renderer) is installed, its backends become available too — same Diagram interface either way:
mmdc.backends()
# ['js'] # mmdr not installed
# ['js', 'merman', 'mermaid-rs-renderer'] # mmdr installed
mmdc.render(source, backend="merman") # returns mmdr's own Diagram directly
CLI
# SVG to stdout (no -o needed)
mmdc -i diagram.mermaid
cat diagram.mermaid | mmdc -i -
# save to file (format from extension)
mmdc -i diagram.mermaid -o diagram.svg
mmdc -i diagram.mermaid -o diagram.png
mmdc -i diagram.mermaid -o diagram.pdf
# size
mmdc -i diagram.mermaid -o diagram.png -w 1200
mmdc -i diagram.mermaid -o diagram.png --scale 2.0
# theme & background
mmdc -i diagram.mermaid -o diagram.svg --theme dark
mmdc -i diagram.mermaid -o diagram.png --background "#f5f5f5"
# PDF options
mmdc -i diagram.mermaid -o diagram.pdf --pdf-format A4 --landscape --margin 1cm
# config & CSS
mmdc -i diagram.mermaid -o diagram.svg --config config.json --css style.css
# info — Mermaid library version
mmdc --info
# version
mmdc --version
Supported Diagram Types
Everything Mermaid v11 itself supports (this bundles the real library, not a subset): flowcharts, sequence diagrams, class diagrams, state diagrams, ER diagrams, Gantt charts, pie charts, git graphs, and more.
Requirements
- Python 3.9+
quickjs-ng,resvg_py(installed automatically)- No system packages, no Node.js, no npm, no browser
Testing
pip install -e ".[test]"
pytest tests/ -v
Contributing
- Fork and create a feature branch
- Add tests for new functionality
- Run
pytest tests/— all must pass - Open a pull request
License
MIT — see LICENSE for details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mmdc-0.6.0-py3-none-any.whl.
File metadata
- Download URL: mmdc-0.6.0-py3-none-any.whl
- Upload date:
- Size: 1.6 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce15a301ac91e3eb2630f2b83cdc1b22f0d8aa4db8768f9a3edeb81462e139f4
|
|
| MD5 |
19b2883a7efdcc7c9756fd85b70b01c2
|
|
| BLAKE2b-256 |
4e3894f4030b8d14cffbda444556d7eb09cdd9f252784581187b6f38832f34ac
|