Mermaid, but headless, in Rust.
Think of merman as Mermaid's headless twin: same language, same diagrams, no browser required.
merman is a Rust, headless re-implementation of Mermaid (baseline: mermaid@11.15.0).
Parity is enforced with golden semantic/layout snapshots and upstream SVG DOM baselines, so
changes that affect semantics, layout, or rendering are caught and reviewed.
Try it in the browser: Merman Playground.
| You want to... | Start with | Notes |
|---|---|---|
| Try or share Mermaid diagrams in the browser | Merman Playground | Static live editor powered by the wasm web package. |
| Render Mermaid from Rust | merman |
Enable render for SVG, ascii for terminal text, raster for PNG/JPG/PDF. |
| Use a command-line tool | merman-cli |
Detect, parse, layout, render SVG, render raster formats, and render ASCII/Unicode text. |
| Embed in a browser or TypeScript app | @merman/web |
wasm-bindgen output plus TypeScript helpers for SVG, JSON, validation, metadata, and DOM rendering. |
| Parse Mermaid or produce semantic JSON | merman-core |
Parser, metadata, semantic JSON, and typed render models without layout/render dependencies. |
| Embed from C, C++, Swift, Kotlin, Dart, Python, or another native host | merman-ffi |
Stable C ABI plus platform wrappers. See FFI protocol, Android, Apple, Flutter/Dart, and Python UniFFI. |
| Work on layout/rendering internals | merman-render |
Low-level layout and SVG stack used by the public merman facade. |
- Semantic JSON for Mermaid diagrams.
- Layout JSON with computed geometry and routes.
- Mermaid-like SVG from a fully headless Rust renderer.
- ASCII/Unicode diagrams for terminals, logs, and documentation snippets.
- PNG, JPG, and PDF via SVG rasterization/conversion.
Diagram coverage and current parity status live in docs/alignment/STATUS.md.
# Command-line tool
cargo install merman-cli
# Rust library: SVG rendering
cargo add merman --features render
# Rust library: ASCII/Unicode text output
cargo add merman --features ascii
# Rust library: SVG + PNG/JPG/PDF
cargo add merman --features rasterFrom a local checkout:
cargo install --path crates/merman-cli
cargo build -p merman-ffi --releaseUse crates/merman-ffi/include/merman.h and link the
platform-specific library artifact from target/release for native embedding.
MSRV is rust-version = 1.87.
- Choose Your Entry Point
- Install
- Quickstart (library)
- Quickstart (CLI)
- Library API details
- Quickstart (FFI and native hosts)
- Math Labels
- ASCII/Unicode text output
- Showcase
- Parity and coverage
- Quality gates
- Limitations
- Architecture notes
- Workspace crates
- Links
- Changelog
- License
For most Rust applications, start with merman::render::HeadlessRenderer:
use merman::render::HeadlessRenderer;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let renderer = HeadlessRenderer::new().with_diagram_id("readme-example");
let svg = renderer
.render_svg_sync("flowchart TD\nA[Start] --> B[Done]")?
.unwrap();
println!("{svg}");
Ok(())
}Use render_svg_sync() when you want Mermaid-parity SVG. Use
render_svg_resvg_safe_sync() when the result will be rasterized or shown by an SVG engine that
does not support <foreignObject> well. Use the ascii feature and
merman::ascii::HeadlessAsciiRenderer for terminal text output.
# Detect diagram type
merman-cli detect path/to/diagram.mmd
# Parse -> semantic JSON
merman-cli parse path/to/diagram.mmd --pretty
# Layout -> layout JSON
merman-cli layout path/to/diagram.mmd --pretty
# Render SVG
merman-cli render path/to/diagram.mmd --out out.svg
# Render terminal text output
merman-cli render --format unicode path/to/diagram.mmd
merman-cli render --format ascii path/to/diagram.mmd
# Terminal text supports common flowchart directions, labels, shapes, and simple subgraphs
printf "flowchart TB\nsubgraph one\nA((Start)) -- go --> B[(DB)]\nend\n" |
merman-cli render --format ascii -
# Render raster formats
merman-cli render --format png --out out.png path/to/diagram.mmd
merman-cli render --format jpg --out out.jpg path/to/diagram.mmd
merman-cli render --format pdf --out out.pdf path/to/diagram.mmdMinimal end-to-end example:
cat > example.mmd <<'EOF'
flowchart TD
A[Start] --> B{Decision}
B -->|Yes| C[Do thing]
B -->|No| D[Do other thing]
EOF
merman-cli render example.mmd --out example.svg
merman-cli render --format ascii example.mmd@'
flowchart TD
A[Start] --> B{Decision}
B -->|Yes| C[Do thing]
B -->|No| D[Do other thing]
'@ | Set-Content -Encoding utf8 example.mmd
merman-cli render example.mmd --out example.svgThe merman crate is a convenience wrapper around merman-core (parsing)
and output crates such as merman-render (layout + SVG) and
merman-ascii (ASCII/Unicode text). Enable the render feature when you
want layout + SVG, ascii when you want text output, and raster when you also need PNG/JPG/PDF
from Rust (no CLI required).
use merman_core::{Engine, ParseOptions};
use merman::render::{
headless_layout_options, render_svg_sync, sanitize_svg_id, SvgRenderOptions,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let engine = Engine::new();
let layout = headless_layout_options();
// For UIs that inline multiple diagrams, set a per-diagram SVG id to avoid internal `<defs>`
// and accessibility id collisions.
let svg_opts = SvgRenderOptions {
diagram_id: Some(sanitize_svg_id("example-diagram")),
..SvgRenderOptions::default()
};
// Executor-free synchronous entrypoint (the work is CPU-bound and does not perform I/O).
let svg = render_svg_sync(
&engine,
"flowchart TD; A-->B;",
ParseOptions::default(),
&layout,
&svg_opts,
)?
.unwrap();
println!("{svg}");
Ok(())
}If you prefer a bundled "pipeline" instead of passing multiple option structs per call, use
merman::render::HeadlessRenderer.
If you already know the diagram type (e.g. from a Markdown fence info string), prefer
Engine::parse_diagram_as_sync(...) to skip type detection.
If your downstream renderer does not support SVG <foreignObject> (common for rasterizers),
prefer HeadlessRenderer::render_svg_resvg_safe_sync(). Use
HeadlessRenderer::render_svg_readable_sync() when you want to keep the original
<foreignObject> nodes and add best-effort <text>/<tspan> fallback overlays.
The split is intentional:
render_svg_syncis for Mermaid-parity snapshots and callers that want the raw SVG contract.render_svg_readable_syncis for inline previews that can keep<foreignObject>but still want readable fallback text.render_svg_resvg_safe_syncorSvgPipeline::resvg_safe()is for PNG/JPG/PDF export and tools built onresvg/usvg.SvgPostprocessorandScopedCssPostprocessorare for host applications that need product-specific theme or cleanup passes after a built-in preset.
render_svg_sync intentionally stays Mermaid-parity by default. For consumer-oriented output,
use an explicit SVG pipeline:
use merman::render::{
CssOverridePolicy, HeadlessRenderer, ScopedCssPostprocessor, SvgPipeline,
};
let renderer = HeadlessRenderer::new().with_diagram_id("readme-diagram");
let pipeline = SvgPipeline::resvg_safe().with_postprocessor(
ScopedCssPostprocessor::new(
r#"
.node rect {
stroke: #2563eb;
stroke-width: 2px;
}
.merman-foreignobject-fallback-text {
fill: #111827;
}
"#,
)
.with_override_policy(CssOverridePolicy::StripExistingImportant),
);
let svg = renderer
.render_svg_with_pipeline_sync("flowchart TD; A[Layer 7\\nHTTP]-->B;", &pipeline)?
.unwrap();
# Ok::<(), Box<dyn std::error::Error>>(())See docs/rendering/SVG_OUTPUT_PIPELINE.md for preset
behavior, custom postprocessors that can read diagram type/title/svg id, and scoped CSS examples.
Runnable example:
cargo run -p merman --features render --example svg_pipeline < fixtures/flowchart/basic.mmd > out.svgThe merman-ffi crate exposes a stable C ABI for non-Rust hosts. The first
release candidate supports SVG rendering, ASCII text rendering, semantic JSON, layout JSON,
validation JSON, binding metadata, and explicit Rust-owned buffer release.
#include "merman.h"
static const uint8_t source[] = "flowchart TD\nA[Hello] --> B[World]";
MermanResult result = merman_render_svg(source, sizeof(source) - 1, NULL, 0);
if (result.code == MERMAN_OK) {
/* result.data contains UTF-8 SVG bytes. */
}
merman_buffer_free(result.data);Every non-empty MermanResult.data buffer must be released with merman_buffer_free. See
docs/bindings/FFI_PROTOCOL.md for result codes, options JSON,
threading, and compatibility rules.
Higher-level wrappers build on the same ABI:
- Android/Kotlin:
docs/bindings/ANDROID_JNI.md - Apple Swift Package:
docs/bindings/APPLE_SWIFT.md - Flutter/Dart FFI:
docs/bindings/FLUTTER_DART_FFI.md - Python UniFFI package:
docs/bindings/PYTHON_UNIFFI.md
Math rendering is optional. Enable ratex-math to render supported $$...$$ labels through the
pure-Rust RaTeX backend. Flowchart and Sequence support math-only labels and single-formula
prose/math labels such as Solve: $$x^2$$:
printf "flowchart LR\nA[\"$$x^2$$\"] --> B\n" |
cargo run -p merman-cli --features ratex-math -- render --math-renderer ratex -Enable the ascii feature when you want terminal-friendly text instead of SVG:
Current public text support covers flowchart/graph, sequenceDiagram, classDiagram, erDiagram, and
xychart through merman::ascii::render_ascii_sync, typed merman::ascii::render_model, the direct
typed helpers (render_flowchart, render_sequence, render_class, render_er,
render_xychart), and merman-cli render --format ascii|unicode.
Flowchart text output covers LR/TD/TB/BT/RL root directions, boxed nodes, common terminal shape approximations, labels, open/dotted/thick edges, length spacing, and titled/nested subgraphs with multiline and wrapped title rows.
Sequence text output covers common messages, notes, lifecycle rows, participant boxes, and the
primary Mermaid control-block subset: loop, opt, break, rect, par_over, alt, par,
and critical. Mermaid-compatible output keeps bottom participant boxes disabled by default;
AsciiRenderOptions::with_sequence_mirror_actors(true) and
merman-cli render --format ascii|unicode --sequence-mirror-actors enable mirrored participant
boxes for terminal output.
Class, ER, and XYChart text output intentionally ship bounded terminal-native subsets: class and ER support boxes, labels, single relationships, layered chain/star multi-relationship layouts, and adjacent-layer crossing layouts resolved by layer reordering. Same-endpoint and simple mixed-parallel relationships render as distinct lanes, simple spanning-level relationships route through side lanes, and isolated unrelated classes/entities render as standalone components beside the relationship layout. Cyclic and denser graph shapes still return clear diagnostics. XYChart renders deterministic compact bars, lines, mixed plots, titles, and axes instead of SVG coordinates.
use merman::ascii::{AsciiRenderOptions, HeadlessAsciiRenderer};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let renderer = HeadlessAsciiRenderer::new()
.with_strict_parsing()
.with_ascii_options(AsciiRenderOptions::unicode());
let text = renderer
.render_ascii_sync("sequenceDiagram\nparticipant A\nparticipant B\nA->>B: Hello")?
.unwrap();
println!("{text}");
Ok(())
}Runnable examples:
cargo run -p merman --features ascii --example ascii_output
cargo run -p merman --features ascii --example ascii_output -- --ascii
printf "flowchart LR\nA --> B\n" | cargo run -p merman-cli --features ascii -- render --format ascii -All screenshots below are produced by merman-cli (headless) and committed under
docs/assets/showcase/.
Each example links to an existing fixture so the README stays honest and reproducible.
Fixture: fixtures/architecture/stress_architecture_batch4_many_groups_sparse_services_069.mmd
Mermaid source
architecture-beta
%% Authored stress fixture (Mermaid@11.12.3): many groups with sparse services (group rect bounds).
group g1(cloud)[G1]
group g2(cloud)[G2]
group g3(cloud)[G3]
group g4(cloud)[G4]
service a(server)[A] in g1
service b(server)[B] in g2
service c(server)[C] in g3
service d(server)[D] in g4
a:R -- L:b
b:R -- L:c
c:R -- L:d
Fixture: fixtures/mindmap/stress_mindmap_br_variants_031.mmd
Mermaid source
mindmap
%% Authored stress fixture (Mermaid@11.12.3): <br> variants inside labels.
root((Root))
n1["line 1<br>line 2"]
n2["line 1<br/>line 2"]
n3["line 1<br />line 2"]
n4["line 1<br \t/>line 2"]
%% plus whitespace variants (see the fixture for the full set)
Fixture: fixtures/sankey/stress_sankey_batch1_dense_shared_nodes_007.mmd
Mermaid source
%%{init: {"sankey": {"width": 900, "height": 420, "useMaxWidth": true, "showValues": false, "linkColor": "source", "nodeAlignment": "justify"}}}%%
sankey
%% Source: repo-ref/mermaid/packages/mermaid/src/docs/syntax/sankey.md (dense graphs) + authored stress
In,A,10
In,B,8
In,C,6
A,X,5
A,Y,5
B,Y,3
B,Z,5
C,X,2
C,Z,4
X,Out 1,7
X,Out 2,0.5
Y,Out 1,6
Y,Out 3,2
Z,Out 2,7
Z,Loss,2
Fixture: fixtures/gantt/upstream_docs_gantt_syntax_002.mmd
Mermaid source
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
excludes weekends
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :until isadded
Functionality added :milestone, isadded, 2014-01-25, 0d
section Documentation
Describe gantt syntax :active, a1, after des1, 3d
Add gantt diagram to demo page :after a1 , 20h
Add another diagram to demo page :doc1, after a1 , 48h
section Last section
Describe gantt syntax :after doc1, 3d
Add gantt diagram to demo page :20h
Add another diagram to demo page :48h
| Architecture (ports + routing) | Mindmap (deep + wide) |
|---|---|
![]() Fixture: fixtures/architecture/stress_architecture_batch5_services_outside_groups_crosslinks_078.mmdNote: Architecture diagonal arrowheads are oriented from the rendered edge segment; DOM parity still normalizes geometry against upstream Mermaid. |
![]() Fixture: fixtures/mindmap/stress_deep_wide_combo_011.mmd |
- Baseline: Mermaid
@11.15.0. - Alignment is enforced via upstream SVG DOM baselines plus semantic/layout golden snapshots.
- DOM parity checks normalize geometry numeric tokens to 3 decimals (
--dom-decimals 3) and compare the canonicalized DOM, not byte-identical SVG text. - Corpus size: 3400+ upstream SVG baselines across 23 diagrams.
- Mermaid diagram families that are present upstream but not implemented here are listed in docs/alignment/STATUS.md.
- Current coverage and gates: docs/alignment/STATUS.md.
- ZenUML is supported in a headless compatibility mode (subset; not parity-gated). See docs/adr/0061-external-diagrams-zenuml.md.
This repo is built around reproducible alignment layers and CI-friendly gates:
- Semantic snapshots:
fixtures/**/*.golden.json - Layout snapshots:
fixtures/**/*.layout.golden.json - Upstream SVG baselines:
fixtures/upstream-svgs/** - DOM parity gates:
xtask compare-all-svgs --check-dom(see docs/adr/0050-release-quality-gates.md)
The goal is not “it looks similar”, but “it stays aligned”.
Quick confidence check:
cargo run -p xtask -- verifyRelease-level check:
cargo run -p xtask -- verify --strict--strict adds all-features compilation, the public feature matrix
(merman no-default/render/raster and merman-core no-default), workspace clippy, override
no-growth, nextest, SVG DOM parity, and full SVG root parity.
For a quick “does raster output look sane?” sweep across fixtures (dev-only):
pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preview/export-fixtures-png.ps1 -BuildReleaseCli -CleanOutDir
- SVG
<foreignObject>HTML labels are not universally supported (especially in rasterizers). If you need a more compatible output, preferrender_svg_resvg_safe_sync()or the explicitSvgPipeline::resvg_safe()preset. - Architecture compound layout and root viewport parity are still geometry-normalized against upstream Cytoscape/FCoSE output; dense compound graphs can still have layout-level differences (see
docs/alignment/STATUS.md). - Determinism is a goal: output is stabilized via goldens, DOM canonicalization, and vendored/forked dependencies where needed (see
roughr-merman).
merman-coreowns detection, parsing, stable semantic JSON, and typed render models for the render-optimized path.merman-renderowns layout and SVG emission. The default SVG helper usesparse_diagram_for_render_model_sync->layout_parsed_render_layout_only->render_layout_svg_parts_for_render_model_with_config, so typed diagrams avoid rebuilding the owned semantic JSON payload.layout_diagram_syncandrender_layouted_svgremain compatibility paths for callers that need owned semantic/layout JSON between steps.- Parity renderers live under
svg/parity/*; large renderers are split by diagram responsibility and generated overrides are treated as compatibility data, not as default model fixes.
| Crate | Role |
|---|---|
merman |
Public Rust facade. Enable render, ascii, and/or raster depending on output needs. |
merman-cli |
Command-line interface for detect/parse/layout/render workflows. |
merman-core |
Detection, parsing, metadata, semantic JSON, and typed render models. |
merman-render |
Headless layout, SVG rendering, SVG pipelines, and raster-friendly postprocessing. |
merman-ascii |
ASCII/Unicode terminal rendering for typed models. |
merman-ffi |
Stable C ABI for native hosts and platform wrappers. |
merman-bindings-core |
Shared safe facade behind C ABI and UniFFI bindings. |
merman-uniffi |
UniFFI-generated binding surface, currently used for Python packaging. |
dugong |
Dagre-compatible layout port. |
dugong-graphlib |
Graph container APIs ported from dagrejs/graphlib. |
manatee |
COSE/FCoSE-style compound graph layout ports. |
roughr-merman |
Forked Rough.js-style renderer dependency stabilized for Mermaid parity. |
- Alignment status: docs/alignment/STATUS.md
- Merman Playground: frankorz.com/merman
- Parity policy: docs/adr/0014-upstream-parity-policy.md
- Release quality gates: docs/adr/0050-release-quality-gates.md
- Upstream Mermaid: mermaid-js/mermaid
- Related: 1jehuang/mermaid-rs-renderer
- ASCII reference: AlexanderGrooff/mermaid-ascii
(MIT; grid/routing/fixture reference for
merman-ascii) - ASCII reference: lukilabs/beautiful-mermaid (MIT; reference for future class, ER, xychart, color, and multiline terminal output)
See CHANGELOG.md.
Dual-licensed under MIT or Apache-2.0. See LICENSE, LICENSE-MIT, LICENSE-APACHE.





