use crate::preview::mermaid::er::{self, Cardinality, ErDiagram, Identification};
use crate::preview::mermaid::flowchart::Stroke;
use crate::preview::mermaid::text_metrics;
use super::edges::Tip;
use super::panel::{self, AttributeRow};
use super::shapes::{self, Glyph};
use super::svg;
use super::{
lay_out_spec, Curve, Diagram, GraphSpec, Label, RenderError, SpecBlock, SpecEdge, SpecNode,
Theme,
};
pub fn render(code: &str, theme: &str) -> Result<String, RenderError> {
let diagram = er::parse(code)?;
let laid = lay_out(&diagram)?;
Ok(svg::emit(&laid, &Theme::named(theme)))
}
pub fn lay_out(diagram: &ErDiagram) -> Result<Diagram, RenderError> {
if !text_metrics::fonts_available() {
return Err(RenderError::NoFonts);
}
if diagram.entities.is_empty() {
return Err(RenderError::NothingToDraw);
}
lay_out_spec(&spec_of(diagram))
}
pub fn spec_of(diagram: &ErDiagram) -> GraphSpec {
let class_of = |name: &str| {
diagram
.class_defs
.iter()
.find(|d| d.name == name)
.map(|d| d.styles.as_slice())
};
let nodes = diagram
.entities
.iter()
.map(|e| {
let attributes: Vec<AttributeRow> = e
.attributes
.iter()
.map(|a| AttributeRow {
kind: Label::measure(&a.kind),
name: Label::measure(&a.name),
keys: Label::measure(&a.keys.join(", ")),
comment: Label::measure(&a.comment),
})
.collect();
let panel = panel::er_panel(&Label::measure(&e.label), &attributes);
SpecNode {
id: e.id.clone(),
glyph: Glyph::ErBox,
label: Label::measure(""),
size: shapes::size(Glyph::ErBox, panel.size),
panel: Some(panel),
style: super::style::cascade(class_of, &e.css_classes, &e.own_styles),
}
})
.collect();
let blocks = diagram
.subgraphs
.iter()
.map(|s| SpecBlock {
id: s.id.clone(),
title: s.title.clone(),
members: s.members.clone(),
dashed: false,
})
.collect();
let edges = diagram
.relationships
.iter()
.map(|r| SpecEdge {
id: r.id.clone(),
from: r.from.clone(),
to: r.to.clone(),
label: r
.label
.as_deref()
.map(Label::measure)
.filter(|l| !l.is_blank()),
tip_start: tip_of(r.from_cardinality),
tip_end: tip_of(r.to_cardinality),
stroke: match r.identification {
Identification::Identifying => Stroke::Normal,
Identification::NonIdentifying => Stroke::Dotted,
},
minlen: 1,
start_label: None,
end_label: None,
style: None,
curve: Curve::Basis,
})
.collect();
GraphSpec {
direction: diagram.direction,
nodes,
edges,
blocks,
}
}
fn tip_of(cardinality: Cardinality) -> Tip {
match cardinality {
Cardinality::OnlyOne => Tip::ErOnlyOne,
Cardinality::ZeroOrOne => Tip::ErZeroOrOne,
Cardinality::ZeroOrMore => Tip::ErZeroOrMore,
Cardinality::OneOrMore => Tip::ErOneOrMore,
Cardinality::MdParent => Tip::None,
}
}