25. October 2026
Convert a Mermaid diagram to PDF with an SVG-only toolchain and you can get boxes and arrows with no words in them, and no error telling you the words are gone. mmd exists because of that failure.
mmd is a command-line tool and Rust library that renders Mermaid diagram source to SVG. It’s pure Rust: no Node, no headless browser, no JavaScript runtime, no C toolchain. It’s for anyone who publishes Mermaid diagrams somewhere other than a web page that runs Mermaid’s own JavaScript, such as a PDF, a static docs build, or an editor preview that wants to draw the diagram itself.
The first release, v2026.9.0, shipped on 19 September with gitGraph and C4 context diagrams. Flowcharts arrived in v2026.10.2 on 13 October. The repository started on 18 August and is now about 3,250 lines of Rust in src/.
By default Mermaid puts diagram labels inside <foreignObject>, which is HTML embedded in the SVG. A browser renders that fine. An SVG-only consumer, such as usvg, which most Rust PDF pipelines use, doesn’t render HTML, so it drops every label. The result is a diagram with the shapes intact and the text missing.
mmd emits real SVG <text> elements. It doesn’t do this as an option you have to remember. There’s no other code path. I checked this against the v2026.10.2 binary with the repository’s own flowchart example: 15 <text> elements and no <foreignObject>.
Styling follows the same rule. Every element carries presentation attributes (fill, stroke, font-size) rather than relying on a CSS stylesheet, because SVG consumers differ in how much CSS they support and a stylesheet is one more way for the output to come out wrong.
$ mmd diagram.mmd # writes diagram.svg
$ mmd diagram.mmd -o out.svg
$ cat diagram.mmd | mmd - -o - > out.svg
As a library it has two entry points. mmd::render(source) returns an SVG string. mmd::scene(source, &measure) returns a laid-out diagram-scene Scene, which is a diagram described as plain data: shapes, containers, routed edges and colours as theme roles. A host that wants to draw the diagram in its own colours paints the scene directly and never goes through SVG. puml and forge produce the same Scene type, so one painter covers all three.
The flowchart example in mmd’s repository happens to describe that design: an editor preview that renders Mermaid, PlantUML and forge sources to one scene, then either paints it natively or writes SVG. Here it is rendered by mmd v2026.10.2. Every label is a <text> element, which is why it survives the trip to PDF.
That one diagram uses a stadium, a rhombus, a subroutine, a cylinder, a parallelogram, a circle and a hexagon, plus solid, thick, dotted, circle-ended and cross-ended links, a subgraph and a classDef.
The crate isn’t on crates.io yet. The design notes say it won’t be until it reaches beta, so library use means a git dependency for now.
Three diagram types are supported:
| Diagram | Coverage |
|---|---|
flowchart / graph |
All four directions, every node shape, the link styles (-->, -.->, ==>, --o, --x, <--> and the rest) with labels, chains and & lists, nested subgraph, classDef, class, ::: and style |
gitGraph |
commit with id:, tag: and type:, branch, checkout/switch, merge |
C4Context |
title, Person, Person_Ext, System, System_Ext, SystemDb, Rel |
The other two, again from the repository’s examples:
That’s a subset, and the boundary is enforced. Anything outside it is a parse error that names the construct and the line, never a diagram with an element quietly missing:
$ mmd cp.mmd
mmd: cp.mmd: line 3: `cherry-pick` is not supported (see DESIGN.md)
$ mmd seq.mmd
mmd: seq.mmd: line 1: unrecognised diagram: expected one of `flowchart`, `gitGraph`, `C4Context`
That second error is the biggest gap today. There are no sequence, class, state or ER diagrams. If those are most of what you draw, mmd isn’t ready for you.
Within flowcharts, click, linkStyle, a subgraph’s own direction and styles on subgraphs are ignored. Mermaid’s %%{init}%% directives and theme engine are deliberately not supported.
Node sizes come from shaping the label with real fonts through rustybuzz, not from emulating a browser’s font metrics. A label is never wider than the box drawn around it. The fonts (Libertinus Serif and DejaVu Sans Mono, through typst-assets) are compiled into the binary, which is why the macOS arm64 binary is 12 MB. A host with its own font stack can implement the Measure trait and turn off the bundled-fonts feature to drop them.
The tradeoff is that boxes are not exactly the size Mermaid would draw. That’s a stated decision: the goal is visual fidelity, not byte-for-byte output matching mermaid-cli.
Before writing mmd I evaluated sebastian 0.6.0, the most complete Rust port of mermaid.js, as a dependency. It’s 39,554 lines. I rejected it for two reasons. It compiles C through core-math-sys to reproduce V8’s trigonometry exactly, and it emits labels as <foreignObject>, which was the problem I set out to fix.
Most of those 39,554 lines go to things mmd doesn’t need: byte-exact decimal formatting, emulating JavaScript map iteration order, a ported JS colour library, the rough.js hand-drawn look, and Mermaid’s CSS theme engine. The finding that shaped the project was that the diagram types that look advanced are the cheap ones. In sebastian, C4 is 618 lines and gitGraph 1,364, and neither needs a graph layout engine. Only flowchart, state, class, ER and block diagrams need dagre, which is about 4,000 lines with its graph library. mmd’s flowcharts use a layered layout of its own instead, in src/layered.rs, about 600 lines.
That’s one data point about one port, not a general claim about Mermaid reimplementations. It was enough to convince me that writing the subset I needed was cheaper than adopting the whole.
Layout is checked on the scene, not by string-matching SVG. Two properties are asserted: nothing is dropped (every parsed element appears in the scene) and nothing overlaps that shouldn’t. Those catch regressions but they don’t tell you a diagram reads well. For that, make showcase renders every supported diagram plus some prose into one PDF, through the same usvg path that loses Mermaid’s labels, and I look at it.
Prebuilt archives for macOS (arm64, x64) and Linux (arm64, x64) are on the releases page, with a SHA256SUMS file. There’s no Windows build yet. With Homebrew:
brew tap grahambrooks/mmd https://github.com/grahambrooks/mmd
brew install grahambrooks/mmd/mmd
Or run the latest release without installing it, using bx, my runner that downloads a tool’s release binary and executes it, the way npx does for Node packages:
bx grahambrooks/mmd -- diagram.mmd -o diagram.svg
The licence is MIT. If you hit a construct mmd rejects and you need it, the error message names it, and that’s exactly what an issue should say.