use super::*;
use merman_core::diagrams::packet::PacketDiagramRenderModel;
fn packet_css(diagram_id: &str) -> String {
let id = escape_xml(diagram_id);
let font = r#""trebuchet ms",verdana,arial,sans-serif"#;
let mut out = String::new();
let _ = write!(
&mut out,
r#"#{}{{font-family:{};font-size:16px;fill:#333;}}"#,
id, font
);
out.push_str(
r#"@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}"#,
);
let _ = write!(
&mut out,
r#"#{} .edge-animation-slow{{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}}#{} .edge-animation-fast{{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}}"#,
id, id
);
let _ = write!(
&mut out,
r#"#{} .error-icon{{fill:#552222;}}#{} .error-text{{fill:#552222;stroke:#552222;}}"#,
id, id
);
let _ = write!(
&mut out,
r#"#{} .edge-thickness-normal{{stroke-width:1px;}}#{} .edge-thickness-thick{{stroke-width:3.5px;}}#{} .edge-pattern-solid{{stroke-dasharray:0;}}#{} .edge-thickness-invisible{{stroke-width:0;fill:none;}}#{} .edge-pattern-dashed{{stroke-dasharray:3;}}#{} .edge-pattern-dotted{{stroke-dasharray:2;}}"#,
id, id, id, id, id, id
);
let _ = write!(
&mut out,
r#"#{} .marker{{fill:#333333;stroke:#333333;}}#{} .marker.cross{{stroke:#333333;}}"#,
id, id
);
let _ = write!(
&mut out,
r#"#{} svg{{font-family:{};font-size:16px;}}#{} p{{margin:0;}}"#,
id, font, id
);
let _ = write!(
&mut out,
r#"#{} .packetByte{{font-size:10px;}}#{} .packetByte.start{{fill:black;}}#{} .packetByte.end{{fill:black;}}#{} .packetLabel{{fill:black;font-size:12px;}}#{} .packetTitle{{fill:black;font-size:14px;}}#{} .packetBlock{{stroke:black;stroke-width:1;fill:#efefef;}}"#,
id, id, id, id, id, id
);
let _ = write!(
&mut out,
r#"#{} :root{{--mermaid-font-family:{};}}"#,
id, font
);
out
}
pub(super) fn render_packet_diagram_svg(
layout: &PacketDiagramLayout,
semantic: &serde_json::Value,
effective_config: &serde_json::Value,
diagram_title: Option<&str>,
options: &SvgRenderOptions,
) -> Result<String> {
let model: PacketDiagramRenderModel = crate::json::from_value_ref(semantic)?;
render_packet_diagram_svg_model(layout, &model, effective_config, diagram_title, options)
}
pub(super) fn render_packet_diagram_svg_model(
layout: &PacketDiagramLayout,
model: &PacketDiagramRenderModel,
_effective_config: &serde_json::Value,
diagram_title: Option<&str>,
options: &SvgRenderOptions,
) -> Result<String> {
let diagram_id = options.diagram_id.as_deref().unwrap_or("merman");
let diagram_id_esc = escape_xml(diagram_id);
let bounds = layout.bounds.clone().unwrap_or(Bounds {
min_x: 0.0,
min_y: 0.0,
max_x: layout.width.max(1.0),
max_y: layout.height.max(1.0),
});
let vb_min_x = bounds.min_x;
let vb_min_y = bounds.min_y;
let vb_w = (bounds.max_x - bounds.min_x).max(1.0);
let vb_h = (bounds.max_y - bounds.min_y).max(1.0);
let mut out = String::new();
let aria_labelledby = model
.acc_title
.as_deref()
.map(|_| format!("chart-title-{diagram_id_esc}"));
let aria_describedby = model
.acc_descr
.as_deref()
.map(|_| format!("chart-desc-{diagram_id_esc}"));
let viewbox_attr = format!(
"{} {} {} {}",
fmt(vb_min_x),
fmt(vb_min_y),
fmt(vb_w),
fmt(vb_h)
);
let style_attr = format!("max-width: {}px; background-color: white;", fmt(vb_w));
root_svg::push_svg_root_open(
&mut out,
root_svg::SvgRootAttrs {
width: root_svg::SvgRootWidth::Percent100,
style_attr: Some(style_attr.as_str()),
viewbox_attr: Some(viewbox_attr.as_str()),
style_viewbox_order: root_svg::SvgRootStyleViewBoxOrder::ViewBoxThenStyle,
aria_labelledby: aria_labelledby.as_deref(),
aria_describedby: aria_describedby.as_deref(),
trailing_newline: false,
..root_svg::SvgRootAttrs::new(diagram_id, "packet")
},
);
if let Some(t) = model.acc_title.as_deref() {
let _ = write!(
&mut out,
r#"<title id="chart-title-{id}">{text}</title>"#,
id = diagram_id_esc,
text = escape_xml(t)
);
}
if let Some(d) = model.acc_descr.as_deref() {
let _ = write!(
&mut out,
r#"<desc id="chart-desc-{id}">{text}</desc>"#,
id = diagram_id_esc,
text = escape_xml(d)
);
}
let css = packet_css(diagram_id);
let _ = write!(&mut out, r#"<style>{}</style>"#, css);
out.push_str(r#"<g/>"#);
for word in &layout.words {
out.push_str("<g>");
for b in &word.blocks {
let _ = write!(
&mut out,
r#"<rect x="{x}" y="{y}" width="{w}" height="{h}" class="packetBlock"/>"#,
x = fmt(b.x),
y = fmt(b.y),
w = fmt(b.width),
h = fmt(b.height)
);
let _ = write!(
&mut out,
r#"<text x="{x}" y="{y}" class="packetLabel" dominant-baseline="middle" text-anchor="middle">{text}</text>"#,
x = fmt(b.x + b.width / 2.0),
y = fmt(b.y + b.height / 2.0),
text = escape_xml(&b.label)
);
if !layout.show_bits {
continue;
}
let is_single_block = b.start == b.end;
let bit_number_y = b.y - 2.0;
let start_x = if is_single_block {
b.x + b.width / 2.0
} else {
b.x
};
let start_anchor = if is_single_block { "middle" } else { "start" };
let _ = write!(
&mut out,
r#"<text x="{x}" y="{y}" class="packetByte start" dominant-baseline="auto" text-anchor="{anchor}">{text}</text>"#,
x = fmt(start_x),
y = fmt(bit_number_y),
anchor = start_anchor,
text = b.start
);
if !is_single_block {
let _ = write!(
&mut out,
r#"<text x="{x}" y="{y}" class="packetByte end" dominant-baseline="auto" text-anchor="end">{text}</text>"#,
x = fmt(b.x + b.width),
y = fmt(bit_number_y),
text = b.end
);
}
}
out.push_str("</g>");
}
let total_row_height = layout.row_height + layout.padding_y;
let title_y = layout.height - total_row_height / 2.0;
let title_from_semantic = model
.title
.as_deref()
.map(str::trim)
.filter(|t| !t.is_empty());
let title_from_meta = diagram_title.map(str::trim).filter(|t| !t.is_empty());
match title_from_semantic.or(title_from_meta) {
Some(title) => {
let _ = write!(
&mut out,
r#"<text x="{x}" y="{y}" dominant-baseline="middle" text-anchor="middle" class="packetTitle">{text}</text>"#,
x = fmt(layout.width / 2.0),
y = fmt(title_y),
text = escape_xml(title)
);
}
None => {
let _ = write!(
&mut out,
r#"<text x="{x}" y="{y}" dominant-baseline="middle" text-anchor="middle" class="packetTitle"/>"#,
x = fmt(layout.width / 2.0),
y = fmt(title_y),
);
}
}
out.push_str("</svg>\n");
Ok(out)
}