merman-render 0.8.0-alpha.5

Headless layout + SVG renderer for Mermaid (parity-focused; upstream SVG goldens).
Documentation

merman-render

Crates.io Documentation License

merman-render is the low-level layout and SVG crate behind merman. It consumes typed merman-core family semantics and produces compatibility layout JSON or Mermaid-like SVG through one family artifact.

Implementation crate: this crate is published to support Merman's Cargo dependency chain, not as the normal product entry point. Applications should depend on merman and use merman::svg::HeadlessRenderer.

Direct use is reserved for Merman maintainers and advanced integrations that deliberately own the typed core model, render session, text measurement, layout, and SVG postprocessing lifecycle.

What It Provides

  • Headless layout for parsed Mermaid diagrams.
  • Mermaid-parity SVG emission.
  • FamilyRenderArtifact, which keeps one matching built-in semantic/layout pair opaque and projects layout JSON or consuming SVG output.
  • LayoutOptions::headless_svg_defaults() for editor/export use cases.
  • Text measurement hooks through TextMeasurer.
  • Math rendering hooks through MathRenderer.
  • Shared Root Viewport policy for computed sizing, accessibility chrome, and root SVG emission.
  • SvgPipeline presets and postprocessors for readable or rasterizer-friendly SVG.

Feature Selection

The base crate provides SVG and the shared Mermaid/Dagre rendering path with no Cargo features. Optional features add only distinct backends or system adapters:

Feature Adds
layout-cytoscape Architecture FCoSE and non-tidy-tree Mindmap COSE-Bilkent layout through manatee.
layout-elk Source-backed ELK layered layout for Flowchart ELK, Class, and ER.
math RaTeX parsing, layout, SVG output, and embedded math fonts.
system-clock, system-timezone, system-random, system-timing Explicit host runtime adapters; none are selected by default.

Omitting an optional layout or math backend preserves parsing and semantic support. Rendering a diagram that needs the missing backend returns a typed capability error instead of silently choosing a different layout.

Render Environment

RenderEnvironment owns adapters and policy for one operation: named text-measurement routes, math rendering, icons, time, randomness, and resource limits. Call begin_session() once before layout and retain that RenderSession through SVG and any raster postprocessing so those phases observe the same snapshot and provenance. The higher-level HeadlessRenderer also applies the frozen session date and timezone to date-sensitive parsing; direct low-level callers are responsible for configuring the core Engine consistently.

TextMeasurer keeps browser DOM primitives distinct. In particular, measure_svg_create_text_bbox_y_offset_px measures ordinary Mermaid createText, while measure_svg_create_text_middle_bbox_y_offset_px measures Architecture's formatted text under an inherited middle baseline. The latter is font- and x-height-dependent and cannot reuse the former. The vendored profile's pinned middle-baseline shift is a deterministic fallback, not a general system-font formula; an authoritative host measurement bypasses it.

This is a breaking replacement for independently configured layout and SVG services. Text and math adapters no longer live in LayoutOptions, and render code does not read process-global policy. Production request values stay in SvgRenderOptions; diagnostics, including timing output, live in SvgDebugOptions and are accepted only by the explicit *_with_debug entry points.

Low-Level Pipeline Example

use merman_core::{Engine, ParseOptions};
use merman_render::{
    environment::RenderEnvironment, family, LayoutOptions,
};
use merman_render::svg::{
    SvgDebugOptions, SvgPipeline, SvgPostprocessMetadata, SvgRenderOptions,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let engine = Engine::new();
    let parsed = engine
        .parse_diagram_for_render_model_sync(
            "flowchart TD\nA[API] --> B[DB]",
            ParseOptions::strict(),
        )?
        .expect("diagram detected");

    let layout_options = LayoutOptions::headless_svg_defaults();
    let session = RenderEnvironment::deterministic().begin_session()?;
    let artifact = family::prepare(parsed, &layout_options, session)?;

    // Compatibility layout JSON projects from this exact typed family artifact.
    let layout_json = artifact.layout_json()?;
    eprintln!("layout family: {}", layout_json["meta"]["diagram_type"]);

    let svg_options = SvgRenderOptions {
        diagram_id: Some("example-diagram".to_string()),
        ..SvgRenderOptions::default()
    };

    // SVG consumes the artifact, so its semantic model and layout cannot be recombined.
    let rendered = artifact.render_svg(&svg_options, &SvgDebugOptions::default())?;
    let (svg, family_kind, metadata, session) = rendered.into_parts();
    assert_eq!(family_kind, family::RenderFamilyKind::Flowchart);
    let pipeline_metadata = SvgPostprocessMetadata::from_svg(&svg)
        .with_family_kind(family_kind)
        .with_diagram_type(metadata.diagram_type)
        .with_optional_diagram_title(metadata.title);
    let svg = SvgPipeline::resvg_safe()
        .process_to_string_with_metadata(&svg, &pipeline_metadata, &session)?;
    println!("{svg}");

    Ok(())
}

SVG Output Pipelines

The default SVG renderer aims for Mermaid DOM parity. Host applications can opt into an output pipeline after rendering:

  • SvgPipeline::parity() leaves the SVG unchanged.
  • SvgPipeline::readable() keeps fallback text for <foreignObject> labels.
  • SvgPipeline::resvg_safe() prepares SVG for common usvg / resvg rasterization paths.
  • ScopedCssPostprocessor, CssOverridePostprocessor, and custom SvgPostprocessor implementations let applications inject host-specific styling without forking the renderer.

See docs/rendering/SVG_OUTPUT_PIPELINE.md for the higher-level integration guide.

Relationship To merman

merman re-exports the common render APIs behind its svg feature and adds HeadlessRenderer, consuming prepare_render_sync stages, SVG id sanitization helpers, and optional raster helpers. Direct merman-render users call family::prepare and retain its RenderSession; the old public layout_parsed*, render_layouted_svg, raw model/layout SVG helpers, and per-family pass-through wrappers are not retained as compatibility paths.