mmdflux 2.1.0

Render Mermaid diagrams as Unicode text, ASCII, SVG, and MMDS JSON.
Documentation
//! Graph-family SVG marker definitions.

use std::collections::BTreeSet;

use super::{GraphSvgPalette, dynamic_css_attrs};
use crate::render::svg::{SvgWriter, fmt_f64};

pub(super) fn render_defs(
    writer: &mut SvgWriter,
    scale: f64,
    palette: &GraphSvgPalette,
    used_marker_ids: &BTreeSet<&'static str>,
) {
    if used_marker_ids.is_empty() {
        return;
    }

    let base = 10.0;
    let half = base / 2.0;
    let marker_size = 8.0 * scale;

    writer.start_tag("<defs>");

    let fill_dynamic_attrs = dynamic_css_attrs(
        palette.dynamic_css,
        "graph-marker-fill",
        &["fill:var(--_arrow);"],
    );
    let stroke_dynamic_attrs = dynamic_css_attrs(
        palette.dynamic_css,
        "graph-marker-stroke",
        &["stroke:var(--_arrow);"],
    );
    let circle_vb = 12.0;
    let circle_marker_size = 12.0 * scale;
    let diamond_size = 12.0;
    let diamond_half = diamond_size / 2.0;
    let diamond_marker_size = 12.0 * scale;

    for marker_id in used_marker_ids {
        match *marker_id {
            "arrowhead" => {
                let marker = format!(
                    "<marker id=\"arrowhead\" viewBox=\"0 0 {base} {base}\" refX=\"{ref_x}\" refY=\"{ref_y}\" markerWidth=\"{mw}\" markerHeight=\"{mh}\" orient=\"auto-start-reverse\" markerUnits=\"userSpaceOnUse\">",
                    base = fmt_f64(base),
                    ref_x = fmt_f64(half),
                    ref_y = fmt_f64(half),
                    mw = fmt_f64(marker_size),
                    mh = fmt_f64(marker_size)
                );
                writer.start_tag(&marker);
                let path = format!(
                    "<path d=\"M 0 0 L {tip} {mid} L 0 {size} z\" fill=\"{color}\"{dynamic_attrs} />",
                    tip = fmt_f64(base),
                    mid = fmt_f64(half),
                    size = fmt_f64(base),
                    color = palette.marker_color,
                    dynamic_attrs = fill_dynamic_attrs.as_str()
                );
                writer.push_line(&path);
                writer.end_tag("</marker>");
            }
            "crosshead" => {
                let cross_size = 11.0;
                let cross_marker_size = 11.0 * scale;
                let marker = format!(
                    "<marker id=\"crosshead\" viewBox=\"0 0 {size} {size}\" refX=\"{ref_x}\" refY=\"{ref_y}\" markerWidth=\"{mw}\" markerHeight=\"{mh}\" orient=\"auto-start-reverse\" markerUnits=\"userSpaceOnUse\">",
                    size = fmt_f64(cross_size),
                    ref_x = fmt_f64(12.0),
                    ref_y = fmt_f64(5.2),
                    mw = fmt_f64(cross_marker_size),
                    mh = fmt_f64(cross_marker_size)
                );
                writer.start_tag(&marker);
                let path = format!(
                    "<path d=\"M 1,1 l 9,9 M 10,1 l -9,9\" stroke=\"{color}\" stroke-width=\"2\"{dynamic_attrs} />",
                    color = palette.marker_color,
                    dynamic_attrs = stroke_dynamic_attrs.as_str()
                );
                writer.push_line(&path);
                writer.end_tag("</marker>");
            }
            "circlehead" => {
                let circle_fill = palette
                    .root_style
                    .background_color
                    .as_deref()
                    .unwrap_or("white");
                let circle_dynamic_attrs = dynamic_css_attrs(
                    palette.dynamic_css,
                    "graph-circle-marker",
                    &["stroke:var(--_arrow);fill:var(--bg);"],
                );
                let marker = format!(
                    "<marker id=\"circlehead\" viewBox=\"0 0 {size} {size}\" refX=\"{ref_x}\" refY=\"{ref_y}\" markerWidth=\"{mw}\" markerHeight=\"{mh}\" orient=\"auto-start-reverse\" markerUnits=\"userSpaceOnUse\">",
                    size = fmt_f64(circle_vb),
                    ref_x = fmt_f64(11.0),
                    ref_y = fmt_f64(6.0),
                    mw = fmt_f64(circle_marker_size),
                    mh = fmt_f64(circle_marker_size)
                );
                writer.start_tag(&marker);
                let circle = format!(
                    "<circle cx=\"6\" cy=\"6\" r=\"5\" stroke=\"{color}\" stroke-width=\"1\" fill=\"{fill}\"{dynamic_attrs} />",
                    color = palette.marker_color,
                    fill = circle_fill,
                    dynamic_attrs = circle_dynamic_attrs.as_str()
                );
                writer.push_line(&circle);
                writer.end_tag("</marker>");
            }
            "open-arrowhead" => {
                let marker = format!(
                    "<marker id=\"open-arrowhead\" viewBox=\"0 0 {base} {base}\" refX=\"{ref_x}\" refY=\"{ref_y}\" markerWidth=\"{mw}\" markerHeight=\"{mh}\" orient=\"auto-start-reverse\" markerUnits=\"userSpaceOnUse\">",
                    base = fmt_f64(base),
                    ref_x = fmt_f64(half),
                    ref_y = fmt_f64(half),
                    mw = fmt_f64(marker_size),
                    mh = fmt_f64(marker_size)
                );
                writer.start_tag(&marker);
                let polygon = format!(
                    "<polygon points=\"0,0 {tip},{mid} 0,{size}\" fill=\"none\" stroke=\"{color}\" stroke-width=\"1\"{dynamic_attrs} />",
                    tip = fmt_f64(base),
                    mid = fmt_f64(half),
                    size = fmt_f64(base),
                    color = palette.marker_color,
                    dynamic_attrs = stroke_dynamic_attrs.as_str()
                );
                writer.push_line(&polygon);
                writer.end_tag("</marker>");
            }
            "diamondhead" => {
                let marker = format!(
                    "<marker id=\"diamondhead\" viewBox=\"0 0 {size} {size}\" refX=\"{ref_x}\" refY=\"{ref_y}\" markerWidth=\"{mw}\" markerHeight=\"{mh}\" orient=\"auto-start-reverse\" markerUnits=\"userSpaceOnUse\">",
                    size = fmt_f64(diamond_size),
                    ref_x = fmt_f64(diamond_half),
                    ref_y = fmt_f64(diamond_half),
                    mw = fmt_f64(diamond_marker_size),
                    mh = fmt_f64(diamond_marker_size)
                );
                writer.start_tag(&marker);
                let polygon = format!(
                    "<polygon points=\"0,{mid} {mid},0 {size},{mid} {mid},{size}\" fill=\"{color}\"{dynamic_attrs} />",
                    mid = fmt_f64(diamond_half),
                    size = fmt_f64(diamond_size),
                    color = palette.marker_color,
                    dynamic_attrs = fill_dynamic_attrs.as_str()
                );
                writer.push_line(&polygon);
                writer.end_tag("</marker>");
            }
            "open-diamondhead" => {
                let marker = format!(
                    "<marker id=\"open-diamondhead\" viewBox=\"0 0 {size} {size}\" refX=\"{ref_x}\" refY=\"{ref_y}\" markerWidth=\"{mw}\" markerHeight=\"{mh}\" orient=\"auto-start-reverse\" markerUnits=\"userSpaceOnUse\">",
                    size = fmt_f64(diamond_size),
                    ref_x = fmt_f64(diamond_half),
                    ref_y = fmt_f64(diamond_half),
                    mw = fmt_f64(diamond_marker_size),
                    mh = fmt_f64(diamond_marker_size)
                );
                writer.start_tag(&marker);
                let polygon = format!(
                    "<polygon points=\"0,{mid} {mid},0 {size},{mid} {mid},{size}\" fill=\"none\" stroke=\"{color}\" stroke-width=\"1\"{dynamic_attrs} />",
                    mid = fmt_f64(diamond_half),
                    size = fmt_f64(diamond_size),
                    color = palette.marker_color,
                    dynamic_attrs = stroke_dynamic_attrs.as_str()
                );
                writer.push_line(&polygon);
                writer.end_tag("</marker>");
            }
            _ => {}
        }
    }

    writer.end_tag("</defs>");
}