Sign in

    diagrams-svg

    Standalone SVG rendering for diagrams.mbt models, in the visual style of archify

    Download zip
    Author
    Version
    0.3.0
    License
    Apache-2.0
    Last updated
    1 hour ago
    Downloads
    42

    #moonrockz/diagrams-svg

    Standalone SVG for the diagram models of diagrams.mbt, in the visual style of archify. The SVG is one file with no scripts, no font data and no external references, so it works in a web page, in a GitHub README and in Markdown.

    The layout and painter cover every sequence element, including self-messages, all arrowhead values, central connections, notes, nested activations, frames with section dividers, box groups, creation and destruction.

    #Render a sequence

    SvgRenderer implements the Renderer trait of moonrockz/diagrams/render. The SVG uses the dark theme by default and the light theme when the viewer prefers light. A data-theme="dark" or data-theme="light" attribute on the root element overrides the viewer preference.

    ///|
    test {
    let s = @sequence.Sequence::new(
    [@sequence.Participant::new("Client"), @sequence.Participant::new("API")],
    [Message(@sequence.Message::new("Client", "API", "GET /forecast"))],
    )
    let r = @render.Renderer::render(@svg.SvgRenderer::new(), Sequence(s))
    assert_eq(r.media_type, "image/svg+xml")
    assert_eq(r.diagnostics.length(), 0)
    let svg = r.output.unwrap()
    assert_true(svg.contains("@media (prefers-color-scheme: light)"))
    assert_true(svg.contains("data-id=\"Client\""))
    }

    #Fixed output and render properties

    Use mode=Fixed(Dark) or mode=Fixed(Light) for slide tools and viewers that need literal SVG presentation attributes. Fixed output has no style element, CSS variables or media rules. Both entry points accept theme and mode.

    Generated marker IDs and adaptive CSS use a deterministic namespace derived from the output, including its palette and mode. For repeated inline copies of the same diagram with separate root overrides, supply a distinct id_prefix to each copy. Both entry points accept it. The prefix is encoded into an XML and CSS safe identifier; labels and other user text stay unchanged.

    ///|
    test {
    let s = @sequence.Sequence::new([], [], title="Login flow")
    let svg = @svg.render_sequence(s, mode=Fixed(Light))
    assert_false(svg.contains("<style"))
    assert_false(svg.contains("var("))
    assert_true(svg.contains("fill=\"#f4f5f7\""))
    }

    A participant's render kind selects its colour family. Actor and client use cyan; service and control use green; database, collections and entity use violet; queue uses orange; boundary uses rose; resource uses amber. Plain participants cycle through cyan, green, violet, amber and orange. Only plain participants advance the cycle. Requests use their sender's family, dotted replies use grey dashed lines, and cross heads use rose. Text uses a shade of the accent that passes 4.5:1 against both the background and opaque label chips. The theme computes the shade by lowering HSL lightness in 0.01 steps.

    Author colours on box groups and rect frames become 18 percent tints over an opaque theme-background mask. Authored alpha multiplies the tint opacity. Supported colours are the CSS named colours, transparent, hex with optional alpha, and comma or space forms of rgb() and rgba(). Context-dependent colours such as currentColor cannot resolve in standalone output. An unreadable colour gives svg/invalid-colour and uses the theme's external fill. Label chips and participant masks keep text contrast independent of those tints.

    Participant and message sublabels appear below the main label. A Smithy shape label that matches its link target shows the short shape name. Participant groups expose data-id; message groups expose data-index, data-from, data-to, data-sender-head and data-receiver-head. Both expose data-link when present, and render descriptions as data-description. These attributes are metadata, so they do not navigate or load a resource. The SVG title prefers the accessibility title, then the diagram title. Its description prefers the accessibility description, then a generated participant and message count.

    #Renderer verification

    mise run test checks stored SVG snapshots of all 38 corpus diagrams in adaptive, fixed dark and fixed light modes on all four targets. Snapshots live under tests/acceptance/__snapshot__/svg/. To refresh them after an intentional paint change, run mise exec -- moon test --target js --update and review the diff.

    mise run test:svg also uses the pinned Playwright Chromium to parse the standalone XML, reject active or external content, check local marker references, exercise both system preferences and both root overrides, and check contrast from the browser's resolved paint colours. The existing site test path runs that browser check with its installed Chromium.

    #Layout geometry

    layout.layout(sequence) is a pure function. It measures monospace text with moonbit-community/metrics, solves column widths and gaps, then advances a row cursor through the ordered events. Multi-line labels accept newlines and <br>, <br/> or <br />. Label.content has normalized newlines; its box covers the widest line and every line's height. The baseline belongs to the first line. Render sublabels, visible message numbers and short Smithy link labels are measured before spacing is calculated.

    Columns retain participant order. A message crossing several gaps distributes any missing width equally among those gaps, with at most one pixel of rounding difference. If activations or group borders block a label, the layout reserves an additional clear interval and places its chip as close to the message centre as that interval permits. Lifelines can pass behind opaque chips. Side notes and self-messages reserve lanes beside their participants. Adjacent message rows are at least 28 px apart; the ordinary row advance is 48 px and grows for multi-line text, self-messages and other events.

    Geometry exposes these values for painters and callers:

    ValueGeometry
    participantsBoxes, labels, optional sublabels, source participants and each participant's lifeline start and end
    messagesChips, labels, optional sublabels, source messages and ordered path segments, including all three segments of a self-message
    notesA measured note box and its text; an over note spans its anchors and places text clear of activation bars
    activationsBars 10 px wide; each nesting depth moves 4 px right; an open activation ends at destruction or diagram end
    framesFrames in preorder, parent indices, header chips and ordered sections with their labels and divider segments
    groupsSource groups, enclosing boxes and title chips; titles use separate member gaps when group extents interleave
    title and title_boxThe measured diagram title and its reserved area

    A newly created participant's box is centred on its following message row, and its lifeline starts below the box. A destruction ends the lifeline at the destroying message and marks the participant for a cross. An activation after creation starts below the new box. The geometry keeps author colour spellings on frame and group values for the painter. The aggregate lifeline_top and lifeline_bottom describe the diagram extent; use the participant's own values for its actual lifetime.

    #Legibility

    The public layout.check(geometry, palette) checks every text block, including frame and section labels, group titles, notes, sublabels and the diagram title. It checks measured width and height, containment in the actual box or chip and canvas, a minimum size of 11 px, and contrast of at least 4.5:1.

    Overlap checks include other labels and opaque chips, participant and note boxes, activation bars, every message path segment, frame and group borders, section dividers and destruction crosses. A label may cover its own element's line or container. Lifelines are excluded because chips can mask them.

    Contrast composes Label.backdrop layers in paint order over the palette background, then composes the text alpha over that result. Layers can be a palette mask, a family fill or an explicit Layer::Tint(Color). Text can use a palette role or an explicit Ink::Color(Color). Palette backgrounds are opaque. Both classic palettes pass the check for all 38 frozen corpus diagrams. The renderer gives each finding as a svg/legibility warning. You can also check a layout with your own palette.

    ///|
    test {
    let s = @sequence.Sequence::new(
    [@sequence.Participant::new("A"), @sequence.Participant::new("B")],
    [Message(@sequence.Message::new("A", "B", "hello"))],
    )
    let g = @layout.layout(s)
    let theme = @theme.Theme::classic()
    assert_eq(@layout.check(g, theme.dark), [])
    assert_eq(@layout.check(g, theme.light), [])
    }

    #BPMN processes and collaborations

    SvgRenderer also accepts Diagram::Bpmn. Its bpmn_policy is PreferDi by default: complete selected DI is painted as supplied; missing or partial DI computes the whole view with a warning. RequireDi requires every visible shape and connection to have geometry. Automatic computes the whole view explicitly. Invalid supplied coordinates return an error under the first two policies. Source conversion and selection are provided by moonrockz/diagrams-bpmn. Choreography and conversation rendering remain deferred.

    Automatic layout condenses cycles, ranks scopes, allocates measured lane/group bands and routes orthogonal connections around shapes and labels within bounded search limits. Its checked module guide describes policies and failure results.

    The separate bpmn-layout package retains exact decimal/exponent tokens for shape bounds, label bounds and edge waypoints. Every element shares one viewport translation, including pools, lanes, expanded subprocesses and boundary events. The painter covers task icons, event definitions, gateway forms, activity and collection markers, data, groups, annotations and all connection kinds. Collapsed scopes hide their contents; message connections can anchor to a visible scope without changing their semantic endpoints. Pools and expanded containers paint behind their contents. Fonts and selected display flags come from typed DI.

    BPMN uses the shared theme's hues with text shades checked at 4.5:1 against its painted backgrounds. Adaptive and fixed modes, accessibility title/description, caller ID prefixes and inert render metadata work for both diagram kinds. bpmn-layout.check reports overlaps, text fit, 11 px minimum size, contrast, containment, boundary attachment and endpoint attachment. It allows intentional container and artifact overlap. Findings never rearrange supplied geometry.

    bpmn_limits bounds nodes, connections, geometry work and output units. Numeric coordinates must be finite, have positive shape extents and absolute magnitude at most one billion. Exhaustion returns diagnostics with no partial output. Generated coordinates use deterministic three-decimal formatting; supplied coordinate tokens stay unchanged. Forbidden XML characters produce an error.

    The rendering corpus under tests/corpus/bpmn-render provides complete supplied process and collaboration examples. Portable snapshots cover all 38 standard graphical kinds, the ten event definitions, activity and gateway markers, theme modes, escaping and ID isolation. mise run test:svg also checks those snapshots in Chromium, including both system preferences, contrast, translated coordinates and offline playback. Default bridge hops and marker approaches update affected sequence snapshots; source identities and semantic content stay stable.

    #Notice

    The colour values, shapes, monospace font stack and spacing of the classic theme come from archify v3.2 by tt-a1i, under the MIT licence. The painter and layout are implemented in MoonBit for this library. In the light theme, six text colours are darker shades of archify's colours, so that text has at least 4.5:1 contrast.

    #Motion scenes

    SvgRenderer::scene returns the ordinary canonical SVG, a script-free display copy, a source catalog and immutable path/node bindings. Both copies use one measured geometry. Only the display copy gains invisible scoped numeric paths; existing symbols, labels, styles, arrowheads and coordinates are unchanged. BPMN bindings inherit the common viewport translation. Sequence bindings retain message containment paths, including self-loops and nested frame occurrences.

    ///|
    test "canonical scene SVG equals the ordinary static renderer" {
    let sequence = @sequence.Sequence::new(
    [@sequence.Participant::new("A"), @sequence.Participant::new("B")],
    [Message(@sequence.Message::new("A", "B", "hello"))],
    )
    let renderer = @svg.SvgRenderer::new()
    let scene = renderer.scene(Sequence(sequence)).scene.unwrap()
    assert_eq(scene.canonical_svg, @svg.render_sequence(sequence))
    assert_false(scene.canonical_svg.contains("data-motion-paths"))
    assert_true(scene.display_svg.contains("data-motion-paths"))
    assert_eq(scene.paths.length(), 1)
    }

    Scenes accept separate catalog/search limits and a conservative total output bound. Sequence scene input also bounds visible participants and text work before layout. Invalid bindings or exhaustion return no partial scene. id_prefix continues to isolate adjacent copies. Standalone playback and canonical download are provided by moonrockz/diagrams-animated.

    #Native Mermaid swimlanes

    Diagram::Swimlane and render_swimlane use the independent immutable diagrams/swimlane model. Mermaid source conversion is in the Mermaid module. Swimlane geometry imports no BPMN model or document types. Existing sequence and BPMN content retain their identities. Connector geometry and bridge treatment have reviewed snapshot updates.

    ///|
    test "swimlane SVG and scene share one actual handoff path" {
    let view = @swimlane.Swimlane::new(
    "Request",
    [
    @swimlane.Node::new("Ask", lane="Customer", shape="stadium"),
    @swimlane.Node::new("Review", lane="Support", shape="diamond"),
    ],
    [@swimlane.Connection::new("handoff", "Ask", "Review", label="request")],
    lanes=[@swimlane.Lane::new("Customer"), @swimlane.Lane::new("Support")],
    direction=LR,
    )
    let renderer = @svg.SvgRenderer::new(id_prefix="request")
    let result = renderer.render(Swimlane(view))
    assert_eq(result.diagnostics, [])
    let scene = renderer.scene(Swimlane(view)).scene.unwrap()
    assert_eq(scene.canonical_svg, result.output.unwrap())
    assert_eq(scene.paths.length(), 1)
    assert_true(scene.canonical_svg.contains("d=\"" + scene.paths[0].path + "\""))
    }

    All four flow directions retain lane order across the flow axis. Local lane and group directions use their own scope. Empty lanes and unowned nodes remain visible. Nested groups reserve header text; collapsed groups hide descendants. Cross-boundary handoffs retain logical endpoints and attach to the visible collapsed ancestor. Internal hidden connections have no visible occurrence.

    The painter supports the pinned Mermaid 12.2.0 vector registry and legacy ellipse form. It keeps distinct shapes, dotted/thick lines and arrow, circle and cross markers. Unknown shapes and image/icon assets return an error with no output. The library never fetches assets. HTML and Markdown labels use an inert text projection and give a warning. Author CSS and callbacks remain source data; links are escaped data-link values without active navigation.

    swimlane_limits bounds visible/input elements, connections, nesting, geometry work and output size. A failed layout, unsupported shape, forbidden XML character or exhausted output budget returns diagnostics with no SVG. Explicit id_prefix values isolate marker IDs and adaptive styles for adjacent inline copies. Adaptive and fixed dark/light palettes keep the existing theme hues with ink checked against canvas and node fills.

    diagrams-svg/swimlane-layout.check checks ownership, measured fit, minimum 11 px text, 4.5:1 contrast, unrelated overlaps, labels and real vector endpoints. Use swimlane-layout.palette for the same ink shades as the painter. Layout computes one geometry for both static rendering and motion bindings. It retains each parallel connection's identity and excludes invisible constraints from SVG and playback. Existing playback and canonical SVG downloads also work for this kind through diagrams-animated.

    #Regex and EBNF railroads

    Diagram::Regex and Diagram::Ebnf, and the render_regex and render_ebnf functions, draw railroads from the immutable diagrams/regex and diagrams/ebnf models. Measured fragments place terms on one baseline. Choices stack below the main line, optional terms have a bypass above, and repeats have a return loop below with its bound label. Each EBNF rule is one labelled track in source order. A top-level sequence that is wider than railroad_limits.width continues on new rows with an explicit continuation path; no text is cut.

    Terms keep their semantic roles: terminals and literals are pills, references are squares, classes and sets have a third style, special sequences a dotted outline and anchors a dashed outline. A lookaround is a dashed frame on the route with its pattern inside, as a constraint region. An exclusion draws the excluded term in a labelled "except" frame below its left term; it is never an alternative route. A reference to a missing rule has a dashed outline and a title. A recursive reference stays one term with a data-target rule identity.

    Each drawn connector has one path in its semantic direction, with the data-connector identity of diagrams/railroad-trace. The SVG contains only these paths, so playback and highlights use the drawn geometry.

    ///|
    test "a regex railroad has one path for each topology connector" {
    let model = @regex.Regex::new("digits", "s", [
    @regex.Node::new("s", Sequence(["a", "r"])),
    @regex.Node::new("a", Anchor(LineStart)),
    @regex.Node::new("r", Repeat(@regex.Bounds::new(1), "d")),
    @regex.Node::new("d", Class(Shorthand('d'))),
    ])
    let result = @svg.render_regex(model, mode=Fixed(Light))
    assert_eq(result.diagnostics, [])
    let svg = result.output.unwrap()
    let topology = @railroad_trace.Topology::regex(model).topology.unwrap()
    assert_eq(
    svg.split("data-connector=\"").count() - 1,
    topology.connectors.length(),
    )
    }

    diagrams-svg/railroad-layout exposes layout and check. The checker finds connectors that do not start and end at their topology points, zero-length connectors, connectors that cross a term or its label, overlapping terms, labels without padding and text or highlight colours with low contrast. railroad_limits bounds nodes, depth, work, width and output. An error gives diagnostics and no SVG.

    #Static trace highlights

    TraceHighlight draws evidence that an external engine supplied. It has four independent channels, each with its own ChannelStyle: matched paths, paths of attempts that did not match, the taken path or prefix, and failure points. A channel can be shown alone, for example only the failure terminal or only the taken prefix. attempts selects attempt identities. Only supplied steps are highlighted; unvisited branches stay neutral.

    Layers have a fixed order and different widths: matched below, not matched above it, the taken path on top, and failure markers last. A connector in two attempts keeps both classifications, each with a data-attempts list and an accessible <title>. Highlights never move terms or paths, and they never change the model or the source. With no highlight the output bytes are the ordinary bytes.

    The trace must belong to the rendered model: kind, model and view identity and the structural fingerprint must agree, and every step must name a drawn term or connector. Spans and failure positions must be inside the input. A stale or foreign trace gives an error and no SVG; targets are never guessed from labels.

    ///|
    test "a failure terminal can be shown without its prefix" {
    let model = @regex.Regex::new("ab", "s", [
    @regex.Node::new("s", Sequence(["a", "b"])),
    @regex.Node::new("a", Literal("a")),
    @regex.Node::new("b", Class(Shorthand('d'))),
    ])
    let request = @railroad_trace.TraceRequest::regex(model, "ax")
    let topology = request.topology().topology.unwrap()
    let start = topology.connector_of("ab", Start).unwrap()
    let trace = @railroad_trace.Trace::new(request, "example", [
    @railroad_trace.Attempt::new(
    "a0",
    NotMatched,
    [
    Traverse(start.id, false),
    Visit("a", Some(@railroad_trace.InputSpan::new(0, 1))),
    ],
    failure=@railroad_trace.FailurePoint::new(Terminal("b"), position=1),
    ),
    ])
    let only_failure = @svg.TraceHighlight::new(
    trace,
    matched=@svg.ChannelStyle::hidden(),
    not_matched=@svg.ChannelStyle::hidden(),
    )
    let svg = @svg.render_regex(model, trace=only_failure).output.unwrap()
    assert_true(svg.contains("rr-trace-failure"))
    assert_false(svg.contains("rr-trace-path"))
    let plain = @svg.render_regex(model).output.unwrap()
    assert_false(plain.contains("rr-trace"))
    }

    SvgRenderer::render_traced is the explicit driver: it calls one RailroadTraceProvider once, validates its result and renders the evidence. Unsupported, Exhausted and ProviderError give the diagnostics trace/unsupported, trace/exhausted and trace/provider-error and no SVG; a completed non-match renders its evidence. render, scene and the animated renderer never call a provider.

    #Structured data

    Diagram::DataTree and render_data_tree draw the diagrams/data-tree model. Each object or array is a box of ordered rows: member rows have a key column and array rows a muted [i] index column. A nested container is a separate box to the right, joined by one drawn connector from its row. A scalar stays in its row, without a connector. Empty containers and a scalar root are visible boxes. Long values wrap and keep every character.

    Scalar kinds stay distinct: text, exact number lexemes, ☑ true/☐ false and ␀ null have different ink and a data-type attribute. Resolved highlights use theme colours with checked contrast; author styles never become CSS. Rows and boxes have data-member and data-node identities, so playback can visit an inline row without a connector path.

    ///|
    test "an inline scalar row has no connector" {
    let tree = @data_tree.DataTree::new("order", "root", [
    @data_tree.Node::object("root", [
    @data_tree.Member::named("m0", "id", "v0"),
    @data_tree.Member::named("m1", "tags", "v1"),
    ]),
    @data_tree.Node::new("v0", Number("12345678901234567890")),
    @data_tree.Node::array("v1", [@data_tree.Member::index("e0", 0, "v2")]),
    @data_tree.Node::new("v2", Text("gift")),
    ])
    let svg = @svg.render_data_tree(tree, mode=Fixed(Dark)).output.unwrap()
    assert_true(svg.contains("12345678901234567890"))
    assert_eq(svg.split("class=\"data-connector\"").count() - 1, 1)
    }

    diagrams-svg/data-tree-layout exposes layout and check for row bounds, label clearance, connector attachment, crossings and contrast. data_tree_limits bounds nodes, depth, work, width and output.

    #Connector styles and final geometry

    ConnectorOptions applies to every diagram kind. Omission keeps natural line geometry and enables bridge hops at unrelated interior crossings. Use Plain for an explicit comparison. minimum_approach defaults to 24 SVG units; automatic routers reserve marked terminal corridors before middle routing. Elbow, Rounded and Curved retain semantic endpoints and graph connectivity.

    ///|
    test "typed styles expose the same immutable final geometry as SVG" {
    let view = @flowchart.Flowchart::new(
    "connectors",
    [
    @flowchart.Node::new("a", label="Request"),
    @flowchart.Node::new("b", label="Done"),
    ],
    [@flowchart.Connection::new("ab", "a", "b")],
    )
    let renderer = @svg.SvgRenderer::new(
    connectors=@svg.ConnectorOptions::new(
    style=Curved,
    crossing=Bridge,
    minimum_approach=32.0,
    ),
    )
    let model = @render.Diagram::Flowchart(view)
    let output = renderer.render(model).output.unwrap()
    let paths = @svg.inspect_connectors(output)
    assert_eq(paths.length(), 1)
    assert_eq(paths[0].id, "ab")
    assert_eq(paths[0].source, "a")
    assert_eq(paths[0].target, "b")
    assert_eq(paths, renderer.connector_geometry(model).paths)
    }

    Connections that land on one node side spread over its usable span with corner and marker padding. Automatic return and crowded routes can use clear top and bottom sides. Explicit architecture ports and edge hints remain in force.

    ResolvedConnector retains original route, final path commands, source identity and optional scoped underpass mask. Original layout route points stay available in the existing layout packages. check_connectors checks final curves and bridges against protected rectangles and viewport bounds. Masks do not exempt shape or label collisions.

    Complete supplied BPMN paths stay unstyled by default. Set style_supplied_bpmn=true to opt into display styling without changing supplied waypoints or source writing. Unsafe geometry returns connector/routing-conflict; invalid numeric settings return connector/invalid-option. ConnectorLimits bounds candidate, subdivision, segment, crossing and output work with a distinct connector/limit failure and no partial successful SVG. See the connector guide for defaults, ownership, resource isolation, canonical export and visual compatibility changes.

    ChannelStyle

    pub struct ChannelStyle {
    visible : Bool
    color :
    Color
    ?
    width : Double
    dashed : Bool
    } derive(Eq,
    Debug
    )

    The visibility and style of one static trace highlight channel. A missing colour selects the theme default for the channel.

    ChannelStyle::equal

    ChannelStyle::hidden

    fn ChannelStyle::hidden() -> ChannelStyle

    A hidden channel.

    ChannelStyle::new

    fn ChannelStyle::new(visible? : Bool, color? :
    Color
    , width? : Double, dashed? : Bool) -> ChannelStyle

    ChannelStyle::not_equal

    fn ChannelStyle::not_equal(x : ChannelStyle, y : ChannelStyle) -> Bool

    ConnectorCommand

    Absolute SVG commands. Source route points remain separate from these values.

    ConnectorCommand::equal

    ConnectorCommand::not_equal

    fn ConnectorCommand::not_equal(x : ConnectorCommand, y : ConnectorCommand) -> Bool

    ConnectorGeometry

    pub struct ConnectorGeometry {
    paths : ReadOnlyArray[ResolvedConnector]
    diagnostics : ReadOnlyArray[
    Diagnostic
    ]
    } derive(Eq,
    Debug
    )

    ConnectorGeometry::equal

    ConnectorGeometry::not_equal

    fn ConnectorGeometry::not_equal(x : ConnectorGeometry, y : ConnectorGeometry) -> Bool

    ConnectorLimits

    pub struct ConnectorLimits {
    candidates : Int
    subdivisions : Int
    segments : Int
    crossings : Int
    output_units : Int
    } derive(Eq,
    Debug
    )

    Per-call connector budgets. Every candidate and generated segment is charged.

    ConnectorLimits::equal

    ConnectorLimits::new

    fn ConnectorLimits::new(candidates? : Int, subdivisions? : Int, segments? : Int, crossings? : Int, output_units? : Int) -> ConnectorLimits

    ConnectorLimits::not_equal

    fn ConnectorLimits::not_equal(x : ConnectorLimits, y : ConnectorLimits) -> Bool

    ConnectorObstacle

    pub(all) struct ConnectorObstacle {
    owner : String
    x : Double
    y : Double
    width : Double
    height : Double
    } derive(Eq,
    Debug
    )

    A protected outline or label rectangle. owner can identify an endpoint whose attachment is intentional. Use an empty owner for unrelated content.

    ConnectorObstacle::equal

    ConnectorObstacle::not_equal

    fn ConnectorObstacle::not_equal(x : ConnectorObstacle, y : ConnectorObstacle) -> Bool

    ConnectorOptions

    pub struct ConnectorOptions {
    style : ConnectorStyle?
    crossing : CrossingMode
    minimum_approach : Double
    corner_radius : Double
    bridge_radius : Double
    style_supplied_bpmn : Bool
    limits : ConnectorLimits
    } derive(Eq,
    Debug
    )

    ConnectorOptions::equal

    ConnectorOptions::new

    fn ConnectorOptions::new(style? : ConnectorStyle, crossing? : CrossingMode, minimum_approach? : Double, corner_radius? : Double, bridge_radius? : Double, style_supplied_bpmn? : Bool, limits? : ConnectorLimits) -> ConnectorOptions

    ConnectorOptions::not_equal

    fn ConnectorOptions::not_equal(x : ConnectorOptions, y : ConnectorOptions) -> Bool

    ConnectorPath

    pub struct ConnectorPath {
    commands : ReadOnlyArray[ConnectorCommand]
    } derive(Eq,
    Debug
    )

    ConnectorPath::equal

    ConnectorPath::new

    Construct immutable absolute geometry, refusing invalid numeric coordinates.

    ConnectorPath::not_equal

    fn ConnectorPath::not_equal(x : ConnectorPath, y : ConnectorPath) -> Bool

    ConnectorPath::parse

    fn ConnectorPath::parse(source : String) -> ConnectorPath?

    Read the renderer's absolute path dialect. No author markup is interpreted.

    ConnectorPath::svg

    fn ConnectorPath::svg(self : ConnectorPath) -> String

    ConnectorPoint

    pub(all) struct ConnectorPoint {
    x : Double
    y : Double
    } derive(Eq,
    Debug
    )

    ConnectorPoint::equal

    ConnectorPoint::not_equal

    fn ConnectorPoint::not_equal(x : ConnectorPoint, y : ConnectorPoint) -> Bool

    ConnectorStyle

    pub(all) enum ConnectorStyle {
    Elbow
    Rounded
    Curved
    } derive(Eq,
    Debug
    )

    Explicit presentation style. Omission preserves intrinsic family geometry.

    ConnectorStyle::equal

    ConnectorStyle::not_equal

    fn ConnectorStyle::not_equal(x : ConnectorStyle, y : ConnectorStyle) -> Bool

    CrossingMode

    pub(all) enum CrossingMode {
    Bridge
    Plain
    } derive(Eq,
    Debug
    )

    CrossingMode::equal

    CrossingMode::not_equal

    fn CrossingMode::not_equal(x : CrossingMode, y : CrossingMode) -> Bool

    Mode

    pub(all) enum Mode {
    Adaptive
    Fixed(Variant)
    } derive(Eq,
    Debug
    )

    Adaptive uses CSS variables; fixed writes literal presentation attributes.

    Mode::equal

    fn Mode::equal(Mode, Mode) -> Bool

    Mode::not_equal

    fn Mode::not_equal(x : Mode, y : Mode) -> Bool

    Mode::to_repr

    NodeBinding

    pub struct NodeBinding {
    endpoint : String
    kind : NodeBindingKind
    }

    NodeBindingKind

    pub(all) enum NodeBindingKind {
    Bpmn(String)
    Message(Int)
    Frame(Int)
    Swimlane(String)
    Flowchart(String)
    Architecture(String)
    RailroadTerm(String)
    DataRow(String)
    DataBox(String)
    } derive(Eq,
    Debug
    )

    NodeBindingKind::equal

    NodeBindingKind::not_equal

    fn NodeBindingKind::not_equal(x : NodeBindingKind, y : NodeBindingKind) -> Bool

    PathBinding

    pub struct PathBinding {
    occurrence :
    Occurrence

    id : String
    path : String
    mask : String?
    }

    ResolvedConnector

    pub struct ResolvedConnector {
    id : String
    source : String
    target : String
    route : ConnectorPath
    path : ConnectorPath
    mask : String?
    } derive(Eq,
    Debug
    )

    One final visible connector and its untouched original route. Identities refer to source connections, message indices or hierarchy/grammar occurrences.

    ResolvedConnector::equal

    ResolvedConnector::not_equal

    fn ResolvedConnector::not_equal(x : ResolvedConnector, y : ResolvedConnector) -> Bool

    Scene

    pub struct Scene {
    canonical_svg : String
    display_svg : String
    catalog :
    Catalog

    paths : ReadOnlyArray[PathBinding]
    nodes : ReadOnlyArray[NodeBinding]
    }

    SceneResult

    pub struct SceneResult {
    scene : Scene?
    diagnostics : ReadOnlyArray[
    Diagnostic
    ]
    }

    SvgRenderer

    A renderer that writes one standalone SVG document.

    SvgRenderer::connector_geometry

    Render and inspect in one layout pass. Diagnostics include refused styling.

    SvgRenderer::render_traced

    fn[P :
    RailroadTraceProvider
    ] SvgRenderer::render_traced(self : SvgRenderer, diagram :
    Diagram
    , provider : P, input : String, start_rule? : String, limits? :
    TraceLimits
    , attempts? : ArrayView[String], matched? : ChannelStyle, not_matched? : ChannelStyle, taken? : ChannelStyle, failure? : ChannelStyle) -> TracedRender

    Invoke provider once for input and render its validated trace with the given channels. This is the only renderer entry point that calls a provider. Unsupported, exhausted and error outcomes give diagnostics and no SVG; a completed non-match renders its evidence.

    SvgRenderer::scene

    fn SvgRenderer::scene(self : SvgRenderer, diagram :
    Diagram
    , sequence_view_id? : String, limits? :
    Limits
    , output_units? : Int) -> SceneResult

    Both copies and their motion bindings use one measured geometry. Ordinary render output remains the canonical artifact; only the display copy gains invisible, scoped numeric paths. No scripts or active references are added.

    TraceHighlight

    pub struct TraceHighlight {
    trace :
    Trace

    attempts : ReadOnlyArray[String]?
    matched : ChannelStyle
    not_matched : ChannelStyle
    taken : ChannelStyle
    failure : ChannelStyle
    limits :
    TraceLimits

    } derive(Eq,
    Debug
    )

    A per-render request to draw supplied trace evidence on a regex or EBNF diagram. Each channel is independent: matched paths, non-matched paths, the taken path or prefix, and failure points. attempts selects attempt identities; None selects every attempt. The request never changes the diagram model, its source or its geometry.

    TraceHighlight::equal

    TraceHighlight::new

    TraceHighlight::not_equal

    fn TraceHighlight::not_equal(x : TraceHighlight, y : TraceHighlight) -> Bool

    TracedRender

    The result of the explicit provider-and-render driver.

    TracedRender::refused

    A traced render that did not reach a provider, with the reasons.

    Variant

    pub(all) enum Variant {
    Dark
    Light
    } derive(Eq,
    Debug
    )

    The palette selected by fixed output.

    Variant::equal

    fn Variant::equal(Variant, Variant) -> Bool

    Variant::not_equal

    fn Variant::not_equal(x : Variant, y : Variant) -> Bool

    Variant::to_repr

    bpmn_geometry_path

    fn bpmn_geometry_path(edge :
    Connection
    , max_units? : Int) -> String?

    Exact authored or deterministically computed path. The limit is checked before appending each segment; no partially returned path escapes.

    bpmn_path

    fn bpmn_path(flow :
    Flow
    ) -> String

    Integer convenience path retained for the first motion-plan API.

    check_connectors

    fn check_connectors(paths : ArrayView[ResolvedConnector], obstacles : ArrayView[ConnectorObstacle], width~ : Double, height~ : Double, limits? : ConnectorLimits) -> ReadOnlyArray[
    Diagnostic
    ]

    Check the actual flattened curves, including every bridge. Underpass masks exempt no shape or label. Intentional endpoint attachment is owner-scoped. Work exhaustion is an error and never a successful incomplete check.

    flowchart_path

    inspect_connectors

    fn inspect_connectors(canonical_svg : String) -> ReadOnlyArray[ResolvedConnector]

    Inspect a rendered artifact. This accepts only this renderer's generated SVG, not general XML. Original routes and final geometry are immutable values.

    render_data_tree

    Render one standalone data-tree SVG with the same caller-supplied options as SvgRenderer. Keys and values are escaped text; a stereotype of a highlight is never style. Highlights use the theme colours.

    render_ebnf

    Render a grammar railroad with the same options as SvgRenderer.

    render_flowchart

    Render a standalone flowchart using the shared themes and vector primitives.

    render_regex

    Render a regex railroad with the same options as SvgRenderer. A supplied trace adds static highlights; no provider is invoked.

    render_sequence

    fn render_sequence(sequence :
    Sequence
    , theme? :
    Theme
    , mode? : Mode, id_prefix? : String, connectors? : ConnectorOptions) -> String

    Render one standalone SVG. Adaptive is the default; fixed selects a palette.

    render_swimlane

    Render a standalone swimlane with the same caller-supplied options as SvgRenderer.

    swimlane_path