Atelier

Renderers

Two exits, two renderers. Renderer\Svg\SvgRenderer draws the geometric form: it renders the Scene a layout engine produced. Renderer\Markdown\MarkdownRenderer writes the textual form: it serializes Mermaid-backed models back to Mermaid source. The Diagram facade fronts both.

SVG

The facade exits cover the common cases:

$diagram = Diagram::of($model);

$svg = $diagram->toSvg();              // compact SVG markup
$diagram->saveSvg('diagram.svg');      // same, written to a file
$document = $diagram->toSvgDocument(); // Atelier\Svg\Document

All three accept an optional Theme (see Theming). saveSvg() throws RuntimeException when the file cannot be written.

SvgRenderer itself maps Scene nodes 1:1 to SVG elements: rects, circles, lines, paths, text, and groups. Coordinates are rounded to 2 decimals. ShapeStyle controls stroke width, dash pattern, line caps, and line joins without exposing SVG types to the layout layer. TextStyle carries font family, size, anchor, fill, and Atelier\Layout\Text\FontWeight, matching the shared text measurement layer used by layout engines. No <marker> elements: arrowheads arrive from layout as filled paths, so the output works in any SVG consumer.

The root SVG carries class="atelier-diagram" and data-renderer="atelier/diagram" so downstream apps can target generated diagrams without parsing internal shapes. When a Scene has a title or description, the renderer emits <title>, <desc>, and role="img".

Post-processing with atelier/svg

toSvgDocument() (or SvgRenderer::renderToDocument()) returns an Atelier\Svg\Document, the entry point to the whole atelier/svg toolbox: optimizer, sanitizer, validator.

use Atelier\Svg\Dumper\CompactXmlDumper;
use Atelier\Svg\Optimizer\Optimizer;
use Atelier\Svg\Optimizer\OptimizerPresets;

$document = Diagram::of($model)->toSvgDocument();

(new Optimizer(OptimizerPresets::web()))->optimize($document);

file_put_contents('diagram.svg', (new CompactXmlDumper())->dump($document));

Markdown / Mermaid

MarkdownRenderer is the inverse of the parser. It renders a model, not a Scene:

$diagram = Diagram::of($stateDiagram);

$markdown = $diagram->toMarkdown();  // ```mermaid fenced block
$mermaid = $diagram->toMermaid();    // raw Mermaid source

Or directly: (new MarkdownRenderer())->render($model) / ->renderMermaid($model). Internally, model-to-serializer dispatch is centralized in MermaidSerializerRegistry so new Mermaid-backed models do not add another instanceof branch to the renderer.

Canonical output

The output is canonical and deterministic: header first, one statement per line indented with 4 spaces, statements in declaration/operation order, no trailing whitespace. It normalizes equivalent spellings: A : description becomes state "description" as A, switch becomes checkout, git commit attributes always appear as id then tag, and auto-declared sequence/flowchart nodes are emitted explicitly. Rendering, parsing, and rendering again yields the same string.

For git graphs the model stores branches and commits, not the original statement sequence, so the serializer reconstructs one by replaying the history. Commit ids that match the builder's auto-id sequence are omitted, so re-parsing regenerates them identically.

What is lost

The supported state grammar has no statement for diagram titles, and the git grammar has no statement for the legend flag, so those are dropped silently. The serialized text re-parses to the same diagram minus that setting. Sequence, flowchart, and C4 titles are serialized. Everything the grammar can express round-trips exactly; see Mermaid support for the guarantees.

Models built directly, bypassing the builders, can be unrepresentable: a state id with a space, a git history not starting on main, a tagged merge commit, or labels containing Mermaid control characters. Those throw InvalidArgumentException with a message naming the offending element.

Serializer rule: Mermaid output must be safely parseable by MermaidParser. If the supported grammar cannot express a model without changing its meaning, the serializer must refuse instead of emitting lossy text. This is tested per serializer with deliberately unrepresentable models.

Shared Mermaid output checks live in Renderer\Markdown\MermaidSerializable: strict ids, field names, single-token attributes, and text control-character guards. Concrete serializers still own their domain-specific messages and ordering rules.

Venn

Venn currently has no text grammar in this package: toMarkdown(), toMermaid() and MarkdownRenderer reject VennDiagram with an InvalidArgumentException.

Below the facade

Advanced callers compose the pieces directly: a layout engine produces a Scene, any Renderer\RendererInterface consumes it.

use Atelier\Diagram\Layout\State\StateLayoutEngine;
use Atelier\Diagram\Renderer\Svg\SvgRenderer;
use Atelier\Diagram\Theme\Theme;

$scene = (new StateLayoutEngine())->layout($stateDiagram, Theme::default());
$svg = (new SvgRenderer())->render($scene);

The Scene is renderer-agnostic: positioned primitives, plain string colors, no SVG types. Other renderers can be added without touching layout.