Skip to content

Latias94/merman

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4,384 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

merman

Mermaid.js, but headless, in Rust.

CI Crates.io Documentation Crates.io Downloads Made with Rust

License: MIT License: Apache 2.0

Mermaid.js is a JavaScript diagramming tool that turns text such as flowchart TD; A-->B; into diagrams for Markdown, docs, and web apps.

Merman is a parity-focused, headless Rust implementation of Mermaid.js for parsing, layout, and browserless rendering. It targets mermaid@11.16.0, produces semantic JSON, layout JSON, SVG, raster formats, and ASCII/Unicode output, and does not launch a browser or JavaScript runtime to render diagrams.

Try it in the browser: Merman Playground.

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.

Project note: Merman is not affiliated with, endorsed by, or sponsored by the Mermaid project or its maintainers. It is an independent compatibility implementation by Mermaid users. Many examples and fixtures in this repository are extracted from Mermaid documentation or tests, either verbatim or with small updates for local context; see THIRD_PARTY_NOTICES.md for Mermaid license and provenance notes.

Choose Your Entry Point

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 / Homebrew Detect, parse, layout, render SVG, render raster formats, and render ASCII/Unicode text.
Lint Mermaid without rendering merman-analysis / merman-cli lint Diagnostics, lint metadata, Markdown/MDX fence handling, and source ranges without pulling in SVG rendering.
Add Mermaid language features to an editor merman-lsp LSP server for diagnostics, completions, hover, symbols, references, rename, folding, semantic tokens, and quick fixes.
Try the VS Code extension preview tools/vscode-extension Local preview, diagnostics, language features, SVG/PNG export, and copy actions. Release CI builds platform VSIX artifacts; Marketplace publishing is tracked as a credential-blocked channel in the release surface contract.
Render diagrams in Rust API docs merman-rustdoc Proc-macro integration for rustdoc that turns Mermaid fences into inline headless SVG.
Embed in a browser or TypeScript app @mermanjs/web wasm-bindgen output plus TypeScript helpers for SVG, JSON, validation, diagnostics-first analysis, metadata, and DOM rendering. Validation JSON remains a compatibility projection during alpha. Source: platforms/web.
Build a Typst plugin/package merman-typst-plugin Experimental wasm-minimal-protocol transport for Typst-compatible WASM hosts.
Parse Mermaid or produce semantic JSON merman-core Parser, metadata, semantic JSON, and typed render models without layout/render dependencies.
Embed from C or C++ merman-ffi Stable C ABI, header, and dynamic/static library artifacts. Source: crates/merman-ffi.
Embed from Swift, Kotlin, Dart, Python, or another native host Native packages and bindings Published packages for Python, Flutter, and web; repository packages for Android and Apple/SwiftPM.
Work on layout/rendering internals merman-render Low-level layout and SVG stack used by the public merman facade.

What Merman Outputs

  • Semantic JSON for Mermaid diagrams.
  • Layout JSON with computed geometry and routes.
  • Diagnostics-first analysis JSON for validation, linting, Markdown scanning, editor integrations, and LSP adapters.
  • 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.

Merman keeps SVG and export outputs as separate contracts. Plain SVG output is Mermaid-parity SVG and may include <foreignObject> HTML labels just like Mermaid.js. PNG/JPG/PDF export, and render_svg_resvg_safe_sync() for hosts that need SVG bytes, run the export-safe pipeline that adds SVG text fallbacks and strips rasterizer-hostile SVG constructs.

Diagram coverage and current parity status live in docs/alignment/STATUS.md.

Sample output

Architecture Mindmap Sankey
Merman-rendered Architecture diagram Merman-rendered Mindmap diagram Merman-rendered Sankey diagram

These images were rendered headlessly by merman-cli from Mermaid fixtures. See Showcase for source diagrams and more rendered examples.

Performance

merman includes a corpus-driven benchmark harness for comparing native merman, mermaid-rs-renderer, and upstream Mermaid JS. The published warm-render standard suite numbers below were measured against Mermaid JS v11.15.0 on Apple M4 and remain historical until the benchmark corpus is refreshed for the active v11.16.0 parity baseline: merman measured all 34 requested fixtures and used about 1.8% to 23.0% of Mermaid JS render time across successful Mermaid JS cases, roughly 4.3x to 56.4x faster, with a median speedup around 15.8x.

The goal is not to replace Mermaid.js in the browser. This is mostly for native apps, text editors, CI pipelines, preview tools, and doc generators where embedding a JS engine just to draw a flowchart is heavy and awkward. Criterion benchmarks are kept for regression tracking, but the main priorities are parity and predictable headless output, not a marketing claim about being "faster Mermaid".

Performance numbers are not a substitute for SVG parity. Missing, skipped, errored, and quality comparison results are reported separately by the benchmark harness. See docs/performance/BENCHMARKING.md for methodology and commands.

Install

# Command-line tool (Cargo)
cargo install merman-cli --version 0.8.0-alpha.3

# Command-line tool (Homebrew, macOS and Linux)
brew install merman-cli

# Rust library: SVG rendering
cargo add merman@0.8.0-alpha.3 --features render

# Rust library: ASCII/Unicode text output
cargo add merman@0.8.0-alpha.3 --features ascii

# Rust library: SVG + PNG/JPG/PDF
cargo add merman@0.8.0-alpha.3 --features raster

# Rust library: diagnostics and lint metadata without rendering
cargo add merman-analysis@0.8.0-alpha.3

# Mermaid language server for editors
cargo install merman-lsp --version 0.8.0-alpha.3

# Rustdoc integration
cargo add merman-rustdoc@0.8.0-alpha.3 --optional

# Browser / TypeScript package
npm install @mermanjs/web

# Flutter package
flutter pub add merman

# Python package (experimental UniFFI wheels)
pip install merman

Package pages: Homebrew merman-cli, npm @mermanjs/web, pub.dev merman, and PyPI merman.

For rustdoc feature setup and examples, see crates/merman-rustdoc/README.md.

From a local checkout:

cargo install --path crates/merman-cli
cargo build -p merman-ffi --release

Use crates/merman-ffi/include/merman.h and link the platform-specific library artifact from target/release for native embedding.

MSRV is rust-version = 1.95.

Contents

Quickstart (library)

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.

Rust examples

The crates/merman/examples programs are ordered as a progressive Rust integration path. Each example reads Mermaid source from stdin when provided and falls back to a small built-in diagram. When stdin is an interactive terminal, examples 01 through 08 and 11 do not wait for input; they print a short note to stderr and render their built-in example. See the crates/merman/examples directory and its README.md for copyable commands with custom stdin and output files.

Step Goal Feature Command
01 Render SVG with the high-level facade render cargo run -p merman --features render --example example_01_svg_basic > out.svg
02 Parse Mermaid to semantic JSON none cargo run -p merman --example example_02_semantic_json
03 Produce layout JSON render cargo run -p merman --features render --example example_03_layout_json
04 Render terminal text ascii cargo run -p merman --features ascii --example example_04_ascii_output
05 Render PNG from Rust raster cargo run -p merman --features raster --example example_05_raster_output -- target/example.png
06 Apply an SVG output pipeline render cargo run -p merman --features render --example example_06_svg_pipeline > pipeline.svg
07 Use Mermaid theme variables and themeCSS render cargo run -p merman --features render --example example_07_theme_css > themed.svg
08 Make time-sensitive Gantt parsing deterministic none cargo run -p merman --example example_08_deterministic_gantt
09 Inline multiple diagrams without SVG id collisions render cargo run -p merman --features render --example example_09_multiple_diagrams
11 Build a custom host output environment render cargo run -p merman --features render --example example_11_custom_output_environment > host-preview.svg

Quickstart (CLI)

# 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.mmd

Minimal 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.svg

Library API details

The 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_with_type_sync(...) to skip type detection.

Rust API migration note for the 0.8 alpha line: merman_core::Error::DiagramParse now stores diagnostic: ParseDiagnostic instead of a raw parse-message field. Existing callers that only display the error can keep using Display / to_string(). Callers that match the enum variant should read diagnostic.message() and can additionally inspect diagnostic.span(), diagnostic.span_kind(), and diagnostic.code() for parser-backed metadata.

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. For CLI output, merman-cli -i diagram.mmd -o diagram.svg --svg-pipeline resvg-safe writes the same export-oriented SVG contract directly.

When you enable the raster feature, PNG/JPG conversion is target-aware and budgeted. A Mermaid SVG can legitimately have a very large viewBox; browser previews usually draw that vector SVG inside a smaller container, while a headless PNG/JPG path must allocate a concrete pixmap. Use RasterOptions::with_fit_to(...) for preview-sized output, scale for device-pixel ratio, and RasterSizeLimit for the final pixmap budget. The default PNG/JPG budget caps output at 8192px per side and 8192*8192 pixels; trusted oversized exports can call RasterOptions::with_unbounded_size().

Runnable raster example:

cargo run -p merman --features raster --example example_05_raster_output
printf "flowchart LR\nA --> B\n" | \
  cargo run -p merman --features raster --example example_05_raster_output -- target/example.png

The split is intentional:

  • render_svg_sync is for Mermaid-parity snapshots and callers that want the raw SVG contract.
  • render_svg_readable_sync is for inline previews that can keep <foreignObject> but still want readable fallback text.
  • render_svg_resvg_safe_sync or SvgPipeline::resvg_safe() is for PNG/JPG/PDF export and tools built on resvg / usvg.
  • SvgPostprocessor and ScopedCssPostprocessor are 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 example_06_svg_pipeline < fixtures/flowchart/basic.mmd > out.svg
cargo run -p merman --features render --example example_11_custom_output_environment > host-preview.svg

Quickstart (FFI and native hosts)

The merman-ffi crate exposes a stable C ABI for non-Rust hosts. The current FFI surface supports SVG rendering, ASCII text rendering, semantic JSON, layout JSON, validation JSON, diagnostics-first analysis JSON, document analysis JSON, analysis facts JSON, lint rule catalog JSON, binding metadata, host text-measurement callbacks for reusable engines, and explicit Rust-owned buffer release.

Validation JSON is a compatibility projection. ADR 0070 defines diagnostics-first analysis JSON as the canonical alpha payload for lint, CI, editor integrations, and LSP adapters; new alpha integrations should track that contract instead of building long-term behavior around the coarse validation result.

Start with the surface that matches your host:

Host Package or source Notes
C / C++ / other native FFI merman-ffi, crates/merman-ffi, merman.h Stable C ABI used by the higher-level wrappers.
Python merman on PyPI, platforms/python/merman Experimental UniFFI wheels with diagram_family_capabilities() and reusable-engine host text measurement through MermanTextMeasurer, reusable_engine_with_text_measurer, set_text_measurer, and clear_text_measurer.
Flutter / Dart merman on pub.dev, platforms/flutter Flutter package backed by Dart FFI and bundled native libraries.
Android / Kotlin platforms/android AAR/JNI package source for Android hosts.
Apple / SwiftPM Package.swift, platforms/apple Swift wrapper and binary XCFramework package layout.
#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, analysis/validation migration notes, threading, and compatibility rules.

Headless rendering cannot know the exact browser or native UI font fallback that will display the final SVG. If precise label geometry matters, native hosts should install the FFI text-measurement callback and measure with their own DOM/canvas/WebView/native text stack. Unsupported requests can return handled=0, and merman will fall back to its vendored Mermaid-compatible measurer. See docs/bindings/HOST_TEXT_MEASUREMENT.md for platform guidance covering Android JNI, Apple Swift, Flutter/Dart FFI, browser/WebView measurement, callback lifetime, and testing.

Use this rule of thumb:

Host scenario Recommended measurement path
CLI, CI, docs generation, server-side batch rendering Use the default vendored metrics. They are deterministic and dependency-light.
Editor or preview rendered in a browser/WebView Use DOM/canvas measurement from the same browser/WebView after fonts are ready, preferably through a cache that the synchronous callback can read.
Android native preview Use TextPaint and StaticLayout with the same font registration and paragraph settings as the preview.
Apple native preview Use Core Text or matching NSAttributedString layout with the same fonts and paragraph attributes as the final view.
Flutter native preview Use the same Flutter paragraph/text layout and font registration as the widget that will display the SVG, and keep the FFI callback on the same isolate.
Any unsupported font, wrap mode, or async-only surface Return unsupported (handled=0 / null) for that request and let merman fall back.

The web package exposes the same idea for browser integrations through renderSvgWithTextMeasurer, layoutJsonWithTextMeasurer, and createBrowserTextMeasurer. The playground uses this as its default preview path and lets maintainers switch between browser and headless measurement plus several font stacks, which is the recommended way to reproduce browser/font-specific clipping reports such as missing trailing punctuation in Flowchart condition labels.

Detailed platform notes:

Binary size

The FFI and wasm packages carry the full parser, layout, and headless renderer stack. Treat them as application/runtime dependencies rather than tiny scripting shims: current release artifacts are roughly 9-17 MB per native dynamic-library slice before app-store or package compression, while browser and Typst WASM artifacts are tracked by docs/release/WASM_SIZE_BUDGETS.json and summarized in docs/release/PACKAGE_SURFACES.md. Universal Apple XCFrameworks and static archives can be larger because they bundle multiple architectures. Use normal platform controls such as release builds, stripping/LTO, package compression, lazy loading, and long-lived caching for versioned artifacts.

Math Labels

merman-cli enables the pure-Rust RaTeX backend by default. Use --math-renderer ratex to render supported $$...$$ labels. 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 -- render --math-renderer ratex -

Build merman-cli with --no-default-features only when you intentionally want to exclude default binary capabilities such as RaTeX and ASCII/Unicode. In that mode ratex remains unavailable unless ratex-math is enabled explicitly, and ASCII/Unicode CLI output remains unavailable unless ascii is enabled explicitly.

ASCII/Unicode text output

Library users enable the ascii feature when they want terminal-friendly text instead of SVG. merman-cli enables ASCII/Unicode output by default:

Current public text support covers Flowchart, Sequence, State, Class, ER, XYChart, Gantt, GitGraph, Journey, Kanban, Mindmap, Packet, Timeline, TreeView, and ZenUML through merman::ascii::render_ascii_sync, typed merman::ascii::render_model, the direct typed helpers, and merman-cli render --format ascii|unicode.

See docs/rendering/ASCII_SUPPORT_MATRIX.md for the current support levels. Some families render full terminal diagrams, while dense or terminal-hostile families intentionally use readable structured summaries. The same support story is exposed through ascii_capabilities / ascii_capabilities_json in the bindings, and the Playground uses that metadata to label full, partial, and summary ASCII support instead of treating support as a simple on/off family list.

Terminal colors use AsciiColorTheme. AsciiColorTheme::from_terminal_palette derives roles from foreground/background plus optional line, accent, muted, surface, and border colors, and bindings expose the same shape as ascii.theme in options JSON. This keeps terminal text theming separate from SVG CSS-variable semantics. Bindings, Web, Playground, and the CLI can pass ASCII-specific options for charset, color mode, terminal palette, sequence mirrored actors, XYChart plot dimensions, grid limits, and relation-summary diagnostics.

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, same-namespace Class relationships route inside namespace containers, and isolated unrelated classes/entities render as standalone components beside the relationship layout. Dense, cyclic, cross-namespace/container, grid-budget-limited, or collision-prone Class/ER layouts fall back to a structured relations: summary instead of drawing unreadable overlapping lines. AsciiRenderOptions::with_relation_summary_diagnostics(true) adds an opt-in reason: row to those summaries so hosts can tell whether fallback came from crossings, route collisions, overlay collisions, or grid budget limits without changing default text output. XYChart renders deterministic compact bars, lines, mixed plots, titles, axes, legends, and configurable compact plot areas instead of SVG coordinates. When showDataLabel is enabled, single bar charts keep compact bar labels, while line and multi-series charts emit explicit values: rows as a terminal replacement for browser hover tooltips.

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 example_04_ascii_output
cargo run -p merman --features ascii --example example_04_ascii_output -- --ascii
cargo run -p merman --features raster --example example_05_raster_output
printf "flowchart LR\nA --> B\n" | cargo run -p merman-cli -- render --format ascii -

Developing

For local Rust changes, start with the fast formatting and test loop:

cargo fmt --all --check
cargo nextest run --workspace
cargo run -p xtask -- verify

Use cargo run -p xtask -- verify --strict before release-level parser, layout, render, or platform binding changes. Platform-specific build and packaging notes live with the binding docs linked in Quickstart (FFI and native hosts).

Showcase

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.

Architecture (many groups + sparse services)

Architecture diagram: many groups + sparse services

Fixture: fixtures/architecture/stress_architecture_batch4_many_groups_sparse_services_069.mmd

Mermaid source
architecture-beta
%% Authored stress fixture: 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
Loading

Mindmap (line breaks in labels)

Mindmap diagram: label line break variants

Fixture: fixtures/mindmap/stress_mindmap_br_variants_031.mmd

Mermaid source
mindmap
  %% Authored stress fixture: <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)
Loading

Sankey (dense shared nodes)

Sankey diagram: dense shared nodes

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
Loading

Gantt (date math + excludes)

Gantt diagram: date math + excludes

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
Loading

Stress gallery (more fixtures)

Architecture (ports + routing) Mindmap (deep + wide)
Architecture diagram: cross-region services + crosslinks
Fixture: fixtures/architecture/stress_architecture_batch5_services_outside_groups_crosslinks_078.mmd
Note: Architecture diagonal arrowheads are oriented from the rendered edge segment; DOM parity still normalizes geometry against upstream Mermaid.
Mindmap diagram: deep + wide tree
Fixture: fixtures/mindmap/stress_deep_wide_combo_011.mmd

Parity and coverage

  • Baseline: Mermaid @11.16.0.
  • Merman treats Mermaid as the specification, not just inspiration: surprising upstream behavior is matched and documented instead of being replaced with a Rust-specific interpretation.
  • Parsing and semantic output are locked with fixtures/**/*.golden.json; layout geometry is locked separately with fixtures/**/*.layout.golden.json so regressions can be traced to parsing, geometry, or final SVG emission.
  • Upstream SVG baselines under fixtures/upstream-svgs/** are generated from the pinned official Mermaid CLI/browser rendering pipeline and used as the end-to-end source of truth.
  • Core layout dependencies are rewritten as headless Rust ports where parity requires matching upstream algorithms: dugong / dugong-graphlib for Dagre + Graphlib behavior, and manatee for Cytoscape/FCoSE/COSE-style compound layouts used by diagrams such as Architecture and Mindmap.
  • Fixture imports are traceable to upstream docs, tests, Cypress rendering samples, and selected stress cases. When an upstream browser sample is not directly renderable by the pinned Mermaid CLI, the raw input is kept as parser-only and a documented normalized variant is used for layout and SVG parity.
  • 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: 3500+ 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.

Quality gates

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 -- verify

Release-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

Fuzzing coverage for parser, render, and SVG pipeline surfaces is documented in docs/security/FUZZING.md.

Limitations

  • SVG <foreignObject> HTML labels are not universally supported (especially in rasterizers). If you need a more compatible output, prefer render_svg_resvg_safe_sync() or the explicit SvgPipeline::resvg_safe() preset.
  • Text measurement is inherently host-sensitive. Merman uses vendored compatibility metrics by default and keeps Flowchart HTML labels non-clipping, but browser and native font fallback, shaping, hinting, and subpixel rounding can still differ. Hosts that need exact geometry should provide a TextMeasurer in Rust, the C FFI text-measurement callback, or Python UniFFI's MermanTextMeasurer.
  • Browser and editor hosts that insert SVG into a DOM should follow the shared rendering security policy in docs/security/RENDERING_SECURITY.md.
  • PNG/JPG export is constrained by a default pixmap budget. This protects headless hosts from oversized allocations, but it also means extremely large diagrams are downscaled unless callers choose a target fit box or explicitly opt into unbounded raster output.
  • 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).

Feature surfaces

Cargo feature meanings and host profile expectations are documented in docs/FEATURES.md. In short, public facade crates such as merman, merman-ffi, merman-wasm, and merman-typst-plugin use core-full for Mermaid's full config/sanitization profile and core-host for local clock, randomness, and timing support. The lower-level merman-core crate names the same profiles full and host.

These core profiles are separate from output features:

Feature Meaning
render Layout, SVG, parse JSON, and layout JSON surfaces.
ascii ASCII/Unicode text rendering surfaces.
raster PNG/JPG/PDF conversion on top of SVG rendering.
analysis Diagnostics, validation projection, document analysis, and lint metadata in binding crates.
core-full Full Mermaid config/frontmatter behavior and full sanitizer parity; larger dependency set.
core-host Ambient host capabilities such as local time, randomness, and parse timing; disable for deterministic sandboxed builds.

merman-wasm is the browser/wasm-bindgen package, while pure-WASM and Typst-style hosts start from merman-core --no-default-features or merman --no-default-features and avoid core-full, core-host, JS, and WASI imports unless the host explicitly permits them.

@mermanjs/web publishes the full browser artifact by default and also exposes package subpaths for users who want less code loaded in browser bundles:

Entry point WASM preset Dependency posture
@mermanjs/web browser-full Default browser package with render, layout, parse, analysis, validation, ASCII, editor-language APIs, and ELK.
@mermanjs/web/core browser-core Smallest analysis-capable browser artifact; no render, layout, parse JSON, ASCII, or editor-language wrappers.
@mermanjs/web/render browser-render SVG/layout/parse plus analysis and validation; no ASCII or editor-language wrappers.
@mermanjs/web/render-only browser-render-only SVG/layout/parse and metadata only; no analysis, validation, lint catalog, ASCII, or editor-language wrappers.
@mermanjs/web/ascii browser-ascii ASCII/Unicode rendering and metadata only; no analysis, SVG/layout/parse, or editor-language wrappers.
@mermanjs/web/full browser-full Explicit full-preset import, equivalent to the default package.

There is no @mermanjs/web/analysis subpath because @mermanjs/web/core is already the smallest analysis-capable browser artifact. Source and CI builds can still use custom presets such as browser-full-no-elk and browser-ratex-math; browser callers can inspect the active artifact with bindingCapabilities().

Typst-compatible WASM uses merman-typst-plugin, not merman-wasm. The plugin crate exports wasm-minimal-protocol functions and is gated so package artifacts import only Typst's typst_env protocol functions.

Use the defaults for normal Rust applications. Defaults keep Mermaid-compatible full configuration/sanitization and host behavior enabled, which is what you want for CLIs, servers, desktop apps, and tests that should accept Mermaid-style frontmatter, JSON5/YAML config, and broad HTML sanitization.

Disable defaults for constrained hosts. For example, a Typst package, plugin sandbox, or no-import WASM runtime usually does not want local wall-clock time, host randomness, wasm-bindgen, WASI, URL parsing, YAML/JSON5 parsing, or DOMPurify-like sanitizer dependencies. In those cases, start from default-features = false, then opt into only the output family you need.

Enable render when you need layout and SVG output. Add raster only when you also need PNG/JPG/PDF conversion. Add ascii only for terminal text output. Enable core-full when you need Mermaid's full config/frontmatter behavior or full sanitizer parity; leave it off for size-oriented Typst-like rendering where inputs and options are already controlled. Enable core-host when local clock/randomness is a feature, such as live previews that should use today’s date; leave it off for deterministic WASM output.

The pure core profile keeps common inline metadata such as @{ shape: rounded }, but does not apply YAML frontmatter title/config and uses conservative HTML escaping instead of DOMPurify-like sanitization.

Architecture notes

  • merman-core owns detection, parsing, stable semantic JSON, and typed render models for the render-optimized path.
  • merman-render owns layout and SVG emission. The default SVG helper uses parse_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_sync and render_layouted_svg remain 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.
  • merman-editor-core owns protocol-neutral language intelligence for editor surfaces: document snapshots, UTF-16 ranges, completion, diagnostics, symbols, navigation, rename, code-action metadata, and semantic-token selection. merman-lsp and the browser WASM package project those shared results into LSP and Monaco-facing shapes instead of carrying separate semantic implementations.
  • The VS Code extension keeps source actions local and source-scoped: Mermaid files and Markdown/MDX Mermaid fences expose preview, export, and copy CodeLens actions that target stable source ids rather than the current cursor position.

Workspace crates

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-rustdoc Proc-macro integration for rendering Mermaid fences in rustdoc as inline headless SVG.
merman-core Detection, parsing, metadata, semantic JSON, and typed render models.
merman-analysis Diagnostics-first payload and source-map contracts for validation, linting, editor integrations, and LSP adapters.
merman-editor-core Protocol-neutral editor language intelligence shared by LSP and browser integrations.
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.
merman-wasm wasm-bindgen transport crate behind the @mermanjs/web TypeScript package.
merman-typst-plugin Experimental wasm-minimal-protocol transport crate for Typst-compatible plugin hosts.
dugong Dagre-compatible layout port.
dugong-graphlib Graph container APIs ported from dagrejs/graphlib.
manatee COSE/FCoSE-style compound graph layout ports.
merman-layout-elk Optional ELK layout engine integration point.
roughr-merman Forked Rough.js-style renderer dependency stabilized for Mermaid parity.

Star History

Star history chart for Latias94/merman

Links

About

Mermaid.js, but headless, in Rust.

Topics

Resources

License

Unknown and 2 other licenses found

Licenses found

Unknown
LICENSE
Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

432 stars

Watchers

2 watching

Forks

Packages

 
 
 

Contributors