use unicode_width::UnicodeWidthStr;
use super::{
canvas::{Canvas, STY_DOT, STY_SOLID, STY_THICK},
drawing::*,
model::{Dir, Graph, LineKind},
painter::{
MermaidArt, MermaidStyles, Oversize, MAX_CANVAS_CELLS, MAX_LABEL, MAX_LINES, PAD,
WRAP_WIDTH,
},
};
mod class;
mod groups;
mod ordering;
mod placement;
use class::draw_class_box;
pub(super) use class::render_class;
use groups::draw_frame;
pub(super) use groups::render_grouped;
use ordering::order_ranks;
use placement::{place_lr, place_td};
pub(super) struct Placed {
pub(super) x: usize,
pub(super) y: usize,
pub(super) w: usize,
pub(super) h: usize,
pub(super) cx: usize,
pub(super) cy: usize,
pub(super) rank: usize,
}
pub(super) struct NodeSizes {
box_w: Vec<usize>,
box_h: Vec<usize>,
lay_w: Vec<usize>,
lay_h: Vec<usize>,
extra_h: Vec<usize>,
self_label_w: Vec<usize>,
}
pub(super) fn layout_flowchart(
graph: &Graph,
styles: &MermaidStyles,
max_width: Option<usize>,
) -> Result<MermaidArt, Oversize> {
let extras: Vec<NodeExtra> = (0..graph.nodes.len()).map(|_| NodeExtra::Plain).collect();
let mut canvas = layout_canvas(graph, &extras, max_width)?;
match graph.dir {
Dir::Up => canvas.flip_vertical(),
Dir::Left => canvas.flip_horizontal(),
_ => {}
}
let (styled_lines, plain_lines) = canvas.to_lines(styles);
Ok(MermaidArt {
styled_lines,
plain_lines,
})
}
pub(super) enum NodeExtra {
Plain,
Frame(Canvas),
Compartments(Vec<Vec<String>>),
}
pub(super) fn layout_canvas(
graph: &Graph,
extras: &[NodeExtra],
max_width: Option<usize>,
) -> Result<Canvas, Oversize> {
let n = graph.nodes.len();
if n == 0 {
return Err(Oversize::Cells);
}
let ranks = compute_ranks(graph);
let max_rank = *ranks.iter().max().unwrap_or(&0);
let mut by_rank: Vec<Vec<usize>> = vec![Vec::new(); max_rank + 1];
for (idx, &r) in ranks.iter().enumerate() {
by_rank[r].push(idx);
}
order_ranks(&mut by_rank, &graph.edges, &ranks);
let wrapped: Vec<Vec<String>> = graph
.nodes
.iter()
.map(|node| wrap_label(&node.label, WRAP_WIDTH, MAX_LINES))
.collect();
let mut box_w: Vec<usize> = (0..n)
.map(|i| match &extras[i] {
NodeExtra::Frame(sub) => {
let title_w = fit_label(&graph.nodes[i].label, WRAP_WIDTH).width();
(sub.w + 2).max(title_w + 4)
}
NodeExtra::Compartments(sections) => {
sections
.iter()
.flatten()
.map(|l| l.width())
.max()
.unwrap_or(1)
.max(1)
+ 2 * PAD
+ 2
}
NodeExtra::Plain => {
wrapped[i]
.iter()
.map(|l| l.width())
.max()
.unwrap_or(1)
.max(1)
+ 2 * PAD
+ 2
}
})
.collect();
let box_h: Vec<usize> = (0..n)
.map(|i| match &extras[i] {
NodeExtra::Frame(sub) => sub.h + 2,
NodeExtra::Compartments(sections) => {
let filled = sections.iter().filter(|s| !s.is_empty()).count();
sections.iter().map(|s| s.len()).sum::<usize>() + filled.saturating_sub(1) + 2
}
NodeExtra::Plain => wrapped[i].len() + 2,
})
.collect();
let mut extra_h = vec![0usize; n];
let mut self_label_w = vec![0usize; n];
for e in &graph.edges {
if e.from == e.to {
extra_h[e.from] = 2;
if let Some(l) = &e.label {
self_label_w[e.from] = self_label_w[e.from].max(l.width().min(MAX_LABEL));
}
}
}
for i in 0..n {
if extra_h[i] > 0 {
box_w[i] = box_w[i].max(7);
}
}
let lay_w: Vec<usize> = (0..n)
.map(|i| {
box_w[i]
+ if self_label_w[i] > 0 {
2 * (self_label_w[i] + 3)
} else {
0
}
})
.collect();
let lay_h: Vec<usize> = (0..n).map(|i| box_h[i] + extra_h[i]).collect();
let sizes = NodeSizes {
box_w,
box_h,
lay_w,
lay_h,
extra_h,
self_label_w,
};
let mut placed: Vec<Placed> = (0..n)
.map(|_| Placed {
x: 0,
y: 0,
w: 0,
h: 0,
cx: 0,
cy: 0,
rank: 0,
})
.collect();
let vertical = matches!(graph.dir, Dir::Down | Dir::Up);
let plan = if vertical {
place_td(&ranks, max_rank, &by_rank, &sizes, graph, &mut placed)
} else {
place_lr(&ranks, max_rank, &by_rank, &sizes, graph, &mut placed)
};
let (canvas_w, canvas_h) = plan.canvas;
if max_width.is_some_and(|max_width| canvas_w > max_width) {
return Err(Oversize::Width);
}
if canvas_w.saturating_mul(canvas_h) > MAX_CANVAS_CELLS {
return Err(Oversize::Cells);
}
let mut canvas = Canvas::new(canvas_w, canvas_h);
for idx in 0..n {
match &extras[idx] {
NodeExtra::Frame(sub) => {
draw_frame(&mut canvas, &placed[idx], &graph.nodes[idx].label, sub)
}
NodeExtra::Compartments(sections) => {
draw_class_box(&mut canvas, &placed[idx], sections)
}
NodeExtra::Plain => draw_box(
&mut canvas,
&placed[idx],
&wrapped[idx],
graph.nodes[idx].shape,
),
}
}
for (i, edge) in graph.edges.iter().enumerate() {
canvas.cur_style = match edge.line {
LineKind::Solid => STY_SOLID,
LineKind::Dotted => STY_DOT,
LineKind::Thick => STY_THICK,
};
if edge.from == edge.to {
route_self(&mut canvas, &placed[edge.from], edge);
continue;
}
let (from, to) = (&placed[edge.from], &placed[edge.to]);
let adjacent = to.rank == from.rank + 1;
let bus = plan.band_end[from.rank] + plan.edge_bus[i];
let lane = plan.lane_base + plan.edge_lane[i];
match (vertical, adjacent) {
(true, true) => route_forward(&mut canvas, from, to, edge, bus),
(true, false) => route_back(&mut canvas, from, to, edge, lane),
(false, true) => route_forward_lr(&mut canvas, from, to, edge, bus),
(false, false) => route_back_lr(&mut canvas, from, to, edge, lane),
}
}
canvas.finalize_mask();
Ok(canvas)
}