merman-render 0.8.0-alpha.6

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 its operation-scoped Renderer with a typed target request.

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 SVG-specific adapters and policy for one operation: named text-measurement routes, math rendering, icons, time, randomness, and resource limits. Low-level callers bind a caller-owned OperationContext and OperationControl with begin_session_in_context() before layout and retain that RenderSession through SVG postprocessing so every phase observes the same snapshot and provenance. The higher-level Renderer captures that context once and also applies its frozen 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 built-in deterministic measurer is a font-agnostic fallback, not a named-font or browser 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, OperationControl, 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 control = OperationControl::new();
    let session = RenderEnvironment::deterministic().begin_session_with_control(control)?;
    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 target-local SVG vocabulary behind its svg feature and owns the source-to-output Renderer, typed target requests, SemanticArtifact, SVG id sanitization, and optional binary targets. Direct merman-render users call family::prepare and retain its RenderSession; the old public raw model/layout SVG helpers and per-family pass-through wrappers are not retained as compatibility paths.