Skip to content

Instantly share code, notes, and snippets.

@addozhang
Last active March 11, 2026 04:27
Show Gist options
  • Select an option

  • Save addozhang/6c2ad1c78c371d50eef95b3df6792313 to your computer and use it in GitHub Desktop.

Select an option

Save addozhang/6c2ad1c78c371d50eef95b3df6792313 to your computer and use it in GitHub Desktop.
Mermaid 图表转图片工具 (PNG/SVG/PDF),基于 Python + Playwright
#!/usr/bin/env python3
"""
mermaid2img.py - Mermaid 图表转图片工具
功能:
- 支持输出格式: PNG / SVG / PDF
- 支持主题: default / dark / forest / neutral / base
- 支持高清缩放 (--scale 2.0)
- 支持背景色设置(含透明背景)
- 支持批量渲染目录下所有 .mmd 文件
- 支持从 stdin 或文件读取 Mermaid 代码
安装依赖:
pip install playwright
playwright install chromium
用法:
echo "graph TD; A-->B" | python3 mermaid2img.py
python3 mermaid2img.py -i diagram.mmd -o output.png
python3 mermaid2img.py -i diagram.mmd -o output.svg --format svg
python3 mermaid2img.py -i diagram.mmd --theme dark --scale 2
python3 mermaid2img.py --batch-input ./diagrams --batch-output ./images
"""
# 依赖安装:
# pip install playwright
# playwright install chromium
import argparse
import sys
import tempfile
import os
from playwright.sync_api import sync_playwright
HTML_TEMPLATE = """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<style>
body {{ margin: 0; padding: 20px; background: {bg_color}; }}
.mermaid {{ display: inline-block; }}
</style>
</head>
<body>
<div class="mermaid">
{code}
</div>
<script>
mermaid.initialize({{
startOnLoad: true,
theme: '{theme}',
securityLevel: 'loose'
}});
</script>
</body>
</html>"""
def render(code: str, output: str, fmt: str = "png", theme: str = "default",
scale: float = 1.0, bg_color: str = "white"):
bg = "transparent" if bg_color == "transparent" else bg_color
html = HTML_TEMPLATE.format(code=code, theme=theme, bg_color=bg)
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(device_scale_factor=scale)
page.set_content(html)
try:
page.wait_for_selector(".mermaid svg", timeout=15000)
except Exception:
print("ERROR: Mermaid 渲染超时,请检查图表语法", file=sys.stderr)
browser.close()
sys.exit(1)
el = page.query_selector(".mermaid")
if fmt == "svg":
svg = page.eval_on_selector(".mermaid svg", "el => el.outerHTML")
with open(output, "w", encoding="utf-8") as f:
f.write(svg)
elif fmt == "pdf":
# 整页 PDF
page.pdf(path=output)
else:
# PNG (默认)
transparent = bg_color == "transparent"
el.screenshot(path=output, omit_background=transparent)
browser.close()
def batch_render(input_dir: str, output_dir: str, **kwargs):
"""批量渲染目录下所有 .mmd 文件"""
os.makedirs(output_dir, exist_ok=True)
files = [f for f in os.listdir(input_dir) if f.endswith(".mmd")]
if not files:
print("没有找到 .mmd 文件", file=sys.stderr)
return
fmt = kwargs.get("fmt", "png")
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(device_scale_factor=kwargs.get("scale", 1.0))
for fname in files:
input_path = os.path.join(input_dir, fname)
output_name = os.path.splitext(fname)[0] + f".{fmt}"
output_path = os.path.join(output_dir, output_name)
with open(input_path, "r", encoding="utf-8") as f:
code = f.read()
html = HTML_TEMPLATE.format(
code=code,
theme=kwargs.get("theme", "default"),
bg_color=kwargs.get("bg_color", "white")
)
page.set_content(html)
try:
page.wait_for_selector(".mermaid svg", timeout=15000)
el = page.query_selector(".mermaid")
if fmt == "svg":
svg = page.eval_on_selector(".mermaid svg", "el => el.outerHTML")
with open(output_path, "w") as f:
f.write(svg)
else:
el.screenshot(path=output_path)
print(f"✅ {fname} → {output_name}")
except Exception as e:
print(f"❌ {fname} 失败: {e}", file=sys.stderr)
browser.close()
def main():
parser = argparse.ArgumentParser(description="Mermaid 图表转图片")
parser.add_argument("-i", "--input", help="输入文件 (.mmd),不指定则从 stdin 读取")
parser.add_argument("-o", "--output", help="输出文件路径(默认 output.png)")
parser.add_argument("--format", choices=["png", "svg", "pdf"], default="png", help="输出格式(默认 png)")
parser.add_argument("--theme", choices=["default", "dark", "forest", "neutral", "base"], default="default", help="主题(默认 default)")
parser.add_argument("--scale", type=float, default=1.0, help="缩放比例,2.0 = 高清(默认 1.0)")
parser.add_argument("--bg", default="white", help="背景色,支持 white/transparent/颜色值(默认 white)")
parser.add_argument("--batch-input", help="批量模式:输入目录(含 .mmd 文件)")
parser.add_argument("--batch-output", help="批量模式:输出目录")
args = parser.parse_args()
# 批量模式
if args.batch_input:
out_dir = args.batch_output or args.batch_input
batch_render(args.batch_input, out_dir,
fmt=args.format, theme=args.theme,
scale=args.scale, bg_color=args.bg)
return
# 读取输入
if args.input:
with open(args.input, "r", encoding="utf-8") as f:
code = f.read()
default_output = os.path.splitext(args.input)[0] + f".{args.format}"
else:
code = sys.stdin.read()
default_output = f"output.{args.format}"
if not code.strip():
print("ERROR: 输入为空", file=sys.stderr)
sys.exit(1)
output = args.output or default_output
print(f"渲染中... 主题={args.theme} 格式={args.format} 缩放={args.scale}x", file=sys.stderr)
render(code, output, fmt=args.format, theme=args.theme,
scale=args.scale, bg_color=args.bg)
size = os.path.getsize(output)
print(f"✅ 已保存: {output} ({size/1024:.1f} KB)", file=sys.stderr)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment