Last active
March 11, 2026 04:27
-
-
Save addozhang/6c2ad1c78c371d50eef95b3df6792313 to your computer and use it in GitHub Desktop.
Mermaid 图表转图片工具 (PNG/SVG/PDF),基于 Python + Playwright
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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