1use crate::Result;
2use crate::model::{Bounds, PacketBlockLayout, PacketDiagramLayout, PacketWordLayout};
3use crate::text::TextMeasurer;
4use serde::Deserialize;
5use serde_json::Value;
6
7#[derive(Debug, Clone, Deserialize)]
8struct PacketBlock {
9 start: i64,
10 end: i64,
11 label: String,
12}
13
14#[derive(Debug, Clone, Deserialize)]
15struct PacketModel {
16 packet: Vec<Vec<PacketBlock>>,
17 title: Option<String>,
18 #[serde(rename = "accTitle")]
19 acc_title: Option<String>,
20 #[serde(rename = "accDescr")]
21 acc_descr: Option<String>,
22}
23
24pub fn layout_packet_diagram(
25 semantic: &serde_json::Value,
26 diagram_title: Option<&str>,
27 effective_config: &serde_json::Value,
28 _measurer: &dyn TextMeasurer,
29) -> Result<PacketDiagramLayout> {
30 let model: PacketModel = crate::json::from_value_ref(semantic)?;
31 let _ = (model.acc_title.as_deref(), model.acc_descr.as_deref());
32
33 fn config_bool(cfg: &Value, path: &[&str]) -> Option<bool> {
34 let mut cur = cfg;
35 for key in path {
36 cur = cur.get(*key)?;
37 }
38 cur.as_bool()
39 }
40
41 fn config_f64(cfg: &Value, path: &[&str]) -> Option<f64> {
42 let mut cur = cfg;
43 for key in path {
44 cur = cur.get(*key)?;
45 }
46 cur.as_f64()
47 .or_else(|| cur.as_i64().map(|n| n as f64))
48 .or_else(|| cur.as_u64().map(|n| n as f64))
49 }
50
51 fn config_i64(cfg: &Value, path: &[&str]) -> Option<i64> {
52 let mut cur = cfg;
53 for key in path {
54 cur = cur.get(*key)?;
55 }
56 cur.as_i64()
57 }
58
59 let show_bits: bool = config_bool(effective_config, &["packet", "showBits"]).unwrap_or(true);
61 let row_height: f64 = config_f64(effective_config, &["packet", "rowHeight"])
62 .unwrap_or(32.0)
63 .max(1.0);
64 let padding_x: f64 = config_f64(effective_config, &["packet", "paddingX"])
65 .unwrap_or(5.0)
66 .max(0.0);
67 let mut padding_y: f64 = config_f64(effective_config, &["packet", "paddingY"])
70 .unwrap_or(5.0)
71 .max(0.0);
72 if show_bits {
73 padding_y += 10.0;
74 }
75 let bit_width: f64 = config_f64(effective_config, &["packet", "bitWidth"])
76 .unwrap_or(32.0)
77 .max(1.0);
78 let bits_per_row: i64 = config_i64(effective_config, &["packet", "bitsPerRow"])
79 .unwrap_or(32)
80 .max(1);
81
82 let total_row_height = row_height + padding_y;
83 let title_from_semantic = model
84 .title
85 .as_deref()
86 .map(str::trim)
87 .filter(|t| !t.is_empty());
88 let title_from_meta = diagram_title.map(str::trim).filter(|t| !t.is_empty());
89 let has_title = title_from_semantic.or(title_from_meta).is_some();
90
91 let words_count = model.packet.len();
92 let svg_height =
93 total_row_height * ((words_count + 1) as f64) - if has_title { 0.0 } else { row_height };
94 let svg_width = bit_width * (bits_per_row as f64) + 2.0;
95
96 let mut words: Vec<PacketWordLayout> = Vec::new();
97 for (row_number, word) in model.packet.iter().enumerate() {
98 let word_y = (row_number as f64) * total_row_height + padding_y;
99 let mut blocks: Vec<PacketBlockLayout> = Vec::new();
100 for block in word {
101 let block_x = ((block.start % bits_per_row) as f64) * bit_width + 1.0;
102 let width = ((block.end - block.start + 1) as f64) * bit_width - padding_x;
103 blocks.push(PacketBlockLayout {
104 start: block.start,
105 end: block.end,
106 label: block.label.clone(),
107 x: block_x,
108 y: word_y,
109 width,
110 height: row_height,
111 });
112 }
113 words.push(PacketWordLayout { blocks });
114 }
115
116 Ok(PacketDiagramLayout {
117 bounds: Some(Bounds {
118 min_x: 0.0,
119 min_y: 0.0,
120 max_x: svg_width,
121 max_y: svg_height.max(1.0),
122 }),
123 width: svg_width,
124 height: svg_height.max(1.0),
125 row_height,
126 padding_x,
127 padding_y,
128 bit_width,
129 bits_per_row,
130 show_bits,
131 words,
132 })
133}