use std::fmt::Write as _;
use super::super::util::{escape_xml, escape_xml_display};
use super::{FlowchartRenderCtx, flowchart_resolve_stroke_for_marker};
pub(in crate::svg::parity::flowchart) struct FlowchartDefs<'a> {
diagram_id: &'a str,
extra_marker_colors: Vec<String>,
}
pub(in crate::svg::parity::flowchart) fn prepare_flowchart_defs<'a>(
diagram_id: &'a str,
ctx: &FlowchartRenderCtx<'_>,
) -> FlowchartDefs<'a> {
FlowchartDefs {
diagram_id,
extra_marker_colors: collect_edge_marker_colors(ctx),
}
}
impl FlowchartDefs<'_> {
pub(in crate::svg::parity::flowchart) fn push_base_markers(&self, out: &mut String) {
push_base_markers(out, self.diagram_id);
}
pub(in crate::svg::parity::flowchart) fn push_extra_markers(&self, out: &mut String) {
push_extra_markers(out, self.diagram_id, &self.extra_marker_colors);
}
}
fn push_base_markers(out: &mut String, diagram_id: &str) {
let id = escape_xml(diagram_id);
let _ = write!(
out,
r#"<marker id="{}_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker>"#,
id.as_str()
);
let _ = write!(
out,
r#"<marker id="{}_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker>"#,
id.as_str()
);
let _ = write!(
out,
r#"<marker id="{}_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker>"#,
id.as_str()
);
let _ = write!(
out,
r#"<marker id="{}_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker>"#,
id.as_str()
);
let _ = write!(
out,
r#"<marker id="{}_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker>"#,
id.as_str()
);
let _ = write!(
out,
r#"<marker id="{}_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker>"#,
id.as_str()
);
}
fn marker_color_id(color: &str) -> String {
let raw = color.trim_end_matches(';');
if raw.trim().is_empty() {
return String::new();
}
let mut out = String::with_capacity(raw.len());
for ch in raw.chars() {
if ch.is_ascii_alphanumeric() {
out.push(ch);
} else {
out.push('_');
}
}
out
}
#[inline]
pub(in crate::svg::parity::flowchart) fn write_flowchart_marker_id_xml(
out: &mut String,
diagram_id: &str,
base: &str,
color: Option<&str>,
) {
let _ = write!(out, "{}", escape_xml_display(diagram_id));
out.push('_');
out.push_str(base);
let Some(color) = color else {
return;
};
let raw = color.trim_end_matches(';');
if raw.trim().is_empty() {
return;
}
out.push('_');
for ch in raw.chars() {
if ch.is_ascii_alphanumeric() {
out.push(ch);
} else {
out.push('_');
}
}
}
fn push_extra_markers(out: &mut String, diagram_id: &str, colors: &[String]) {
for c in colors {
let cid = marker_color_id(c);
if cid.is_empty() {
continue;
}
let _ = write!(
out,
r#"<marker id="{}_flowchart-v2-pointEnd_{}" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;" stroke="{}" fill="{}"/></marker>"#,
escape_xml(diagram_id),
escape_xml(&cid),
escape_xml_display(c.trim()),
escape_xml_display(c.trim())
);
}
}
fn collect_edge_marker_colors(ctx: &FlowchartRenderCtx<'_>) -> Vec<String> {
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut out: Vec<String> = Vec::new();
for e in ctx.edges_by_id.values() {
let mut found: Option<String> = None;
for raw in ctx.default_edge_style.iter().chain(e.style.iter()) {
let s = raw.trim_start();
let Some(rest) = s.strip_prefix("stroke:") else {
continue;
};
let cid = marker_color_id(rest);
if cid.is_empty() {
continue;
}
if seen.insert(cid) {
found = Some(rest.to_string());
}
break;
}
if found.is_none() && !e.classes.is_empty() {
let stroke = flowchart_resolve_stroke_for_marker(
ctx.class_defs,
&e.classes,
&ctx.default_edge_style,
&e.style,
);
if let Some(stroke) = stroke {
let cid = marker_color_id(&stroke);
if !cid.is_empty() && seen.insert(cid) {
found = Some(stroke);
}
}
}
if let Some(v) = found {
out.push(v);
}
}
out.sort();
out
}