use super::super::*;
pub(in crate::svg::parity::flowchart) struct FlowchartRootRenderSession<'details, 'cache> {
pub(in crate::svg::parity::flowchart) timing_enabled: bool,
pub(in crate::svg::parity::flowchart) details: &'details mut FlowchartRenderDetails,
pub(in crate::svg::parity::flowchart) edge_cache:
Option<&'cache FxHashMap<&'cache str, FlowchartEdgePathCacheEntry>>,
}
pub(in crate::svg::parity::flowchart) fn render_flowchart_root(
out: &mut String,
ctx: &FlowchartRenderCtx<'_>,
cluster_id: Option<&str>,
parent_origin_x: f64,
parent_origin_y: f64,
session: &mut FlowchartRootRenderSession<'_, '_>,
) {
session.details.root_calls += 1;
let (origin_x, origin_y, transform_attr) = if let Some(cid) = cluster_id {
if let Some(off) = flowchart_cluster_root_offsets(ctx, cid) {
let rel_x = off.origin_x - parent_origin_x;
let rel_y = off.abs_top_transform - parent_origin_y;
(
off.origin_x,
off.origin_y,
format!(
r#" transform="translate({},{})""#,
fmt_display(rel_x),
fmt_display(rel_y)
),
)
} else {
(
parent_origin_x,
parent_origin_y,
r#" transform="translate(0,0)""#.to_string(),
)
}
} else {
(0.0, 0.0, String::new())
};
let _ = write!(out, r#"<g class="root"{}>"#, transform_attr);
let content_origin_y = origin_y;
let _g_clusters = detail_guard(session.timing_enabled, &mut session.details.clusters);
let mut clusters_to_draw: Vec<&LayoutCluster> = Vec::new();
if let Some(cid) = cluster_id {
if ctx
.subgraphs_by_id
.get(cid)
.is_some_and(|sg| sg.nodes.is_empty())
{
} else if let Some(cluster) = ctx.layout_clusters_by_id.get(cid) {
clusters_to_draw.push(cluster);
}
}
for id in ctx.subgraphs_by_id.keys() {
if cluster_id.is_some_and(|cid| cid == *id) {
continue;
}
if ctx
.subgraphs_by_id
.get(id)
.is_some_and(|sg| sg.nodes.is_empty())
{
continue;
}
if ctx.recursive_clusters.contains(id) {
continue;
}
if flowchart_effective_parent(ctx, id) == cluster_id {
if let Some(cluster) = ctx.layout_clusters_by_id.get(*id) {
clusters_to_draw.push(cluster);
}
}
}
if clusters_to_draw.is_empty() {
out.push_str(r#"<g class="clusters"/>"#);
} else {
fn is_ancestor(parent: &FxHashMap<&str, &str>, ancestor: &str, node: &str) -> bool {
let mut cur: Option<&str> = Some(node);
while let Some(id) = cur {
let Some(p) = parent.get(id).copied() else {
break;
};
if p == ancestor {
return true;
}
cur = Some(p);
}
false
}
clusters_to_draw.sort_by(|a, b| {
if a.id != b.id {
if is_ancestor(&ctx.parent, &a.id, &b.id) {
return std::cmp::Ordering::Less;
}
if is_ancestor(&ctx.parent, &b.id, &a.id) {
return std::cmp::Ordering::Greater;
}
}
let a_top_y = a.y - a.height / 2.0;
let b_top_y = b.y - b.height / 2.0;
let a_top_x = a.x - a.width / 2.0;
let b_top_x = b.x - b.width / 2.0;
let a_idx = ctx
.subgraph_order
.iter()
.position(|id| *id == a.id.as_str());
let b_idx = ctx
.subgraph_order
.iter()
.position(|id| *id == b.id.as_str());
if let (Some(ai), Some(bi)) = (a_idx, b_idx) {
bi.cmp(&ai)
.then_with(|| b_top_y.total_cmp(&a_top_y))
.then_with(|| b_top_x.total_cmp(&a_top_x))
.then_with(|| a.id.cmp(&b.id))
} else {
b_top_y
.total_cmp(&a_top_y)
.then_with(|| b_top_x.total_cmp(&a_top_x))
.then_with(|| a.id.cmp(&b.id))
}
});
out.push_str(r#"<g class="clusters">"#);
for cluster in clusters_to_draw {
render_flowchart_cluster(out, ctx, cluster, origin_x, content_origin_y);
}
out.push_str("</g>");
}
drop(_g_clusters);
let _g_edges_select = detail_guard(session.timing_enabled, &mut session.details.edges_select);
let edges = flowchart_edges_for_root(ctx, cluster_id);
drop(_g_edges_select);
let _g_edge_paths = detail_guard(session.timing_enabled, &mut session.details.edge_paths);
if edges.is_empty() {
out.push_str(r#"<g class="edgePaths"/>"#);
} else {
out.push_str(r#"<g class="edgePaths">"#);
let mut scratch = FlowchartEdgeDataPointsScratch::default();
for e in &edges {
render_flowchart_edge_path(
out,
ctx,
e,
origin_x,
content_origin_y,
&mut scratch,
session.edge_cache,
);
}
out.push_str("</g>");
}
drop(_g_edge_paths);
let _g_edge_labels = detail_guard(session.timing_enabled, &mut session.details.edge_labels);
if edges.is_empty() {
out.push_str(r#"<g class="edgeLabels"/>"#);
} else {
fn edge_label_is_empty(
ctx: &FlowchartRenderCtx<'_>,
edge: &crate::flowchart::FlowEdge,
) -> bool {
let label_text = edge.label.as_deref().unwrap_or_default();
let label_type = edge.label_type.as_deref().unwrap_or("text");
let label_plain =
flowchart_label_plain_text(label_text, label_type, ctx.edge_html_labels);
label_plain.trim().is_empty() && label_text.trim().is_empty()
}
out.push_str(r#"<g class="edgeLabels">"#);
if !ctx.edge_html_labels {
for e in &edges {
if edge_label_is_empty(ctx, e) {
out.push_str(r#"<g><rect class="background" style="stroke: none"/></g>"#);
}
}
for e in &edges {
render_flowchart_edge_label(
out,
ctx,
e,
origin_x,
content_origin_y,
session.edge_cache,
);
}
} else {
for e in &edges {
render_flowchart_edge_label(
out,
ctx,
e,
origin_x,
content_origin_y,
session.edge_cache,
);
}
}
out.push_str("</g>");
}
drop(_g_edge_labels);
out.push_str(r#"<g class="nodes">"#);
let _g_dom_order = detail_guard(session.timing_enabled, &mut session.details.dom_order);
let mut dom_order: Vec<&str> = ctx
.dom_node_order_by_root
.get(cluster_id.unwrap_or(""))
.map(|ids| ids.iter().map(|s| s.as_str()).collect())
.unwrap_or_default();
if !dom_order.is_empty() {
let mut emits_anything = false;
for id in &dom_order {
if ctx
.subgraphs_by_id
.get(id)
.is_some_and(|sg| !sg.nodes.is_empty())
{
if ctx.recursive_clusters.contains(id) {
emits_anything = true;
break;
}
continue;
}
emits_anything = true;
break;
}
if !emits_anything {
dom_order.clear();
}
}
if dom_order.is_empty() {
dom_order = flowchart_root_children_nodes(ctx, cluster_id);
dom_order.extend(flowchart_root_children_clusters(ctx, cluster_id));
}
drop(_g_dom_order);
for id in dom_order {
if ctx
.subgraphs_by_id
.get(id)
.is_some_and(|sg| !sg.nodes.is_empty())
{
if ctx.recursive_clusters.contains(id) {
let nested_start = session.timing_enabled.then(std::time::Instant::now);
render_flowchart_root(out, ctx, Some(id), origin_x, origin_y, session);
if let Some(s) = nested_start {
session.details.nested_roots += s.elapsed();
}
}
continue;
}
let node_start = session.timing_enabled.then(std::time::Instant::now);
render_flowchart_node(
out,
ctx,
id,
origin_x,
content_origin_y,
session.timing_enabled,
&mut *session.details,
);
if let Some(s) = node_start {
session.details.nodes += s.elapsed();
}
}
out.push_str("</g></g>");
}
pub(super) fn flowchart_wrap_svg_text_lines(
measurer: &dyn TextMeasurer,
text: &str,
style: &crate::text::TextStyle,
max_width_px: Option<f64>,
break_long_words: bool,
) -> Vec<String> {
crate::text::wrap_svg_text_lines_by_measurement(
measurer,
text,
style,
max_width_px,
break_long_words,
)
}