Skip to main content

merman_render/
packet.rs

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    // Mermaid 11.12.2 defaults (see `repo-ref/mermaid/.../packet/db.ts` + `DEFAULT_CONFIG.packet`).
60    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    // Mermaid applies `showBits` after merging defaults+user config by bumping `paddingY` by +10.
68    // See `repo-ref/mermaid/packages/mermaid/src/diagrams/packet/db.ts`.
69    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}