Skip to main content

merman_render/
packet.rs

1use crate::Result;
2use crate::config::config_f64;
3use crate::model::{Bounds, PacketBlockLayout, PacketDiagramLayout, PacketWordLayout};
4use crate::text::TextMeasurer;
5use merman_core::diagrams::packet::PacketDiagramRenderModel;
6use serde_json::Value;
7
8pub fn layout_packet_diagram(
9    semantic: &serde_json::Value,
10    diagram_title: Option<&str>,
11    effective_config: &serde_json::Value,
12    _measurer: &dyn TextMeasurer,
13) -> Result<PacketDiagramLayout> {
14    let model: PacketDiagramRenderModel = crate::json::from_value_ref(semantic)?;
15    layout_packet_diagram_typed(&model, diagram_title, effective_config, _measurer)
16}
17
18pub fn layout_packet_diagram_typed(
19    model: &PacketDiagramRenderModel,
20    diagram_title: Option<&str>,
21    effective_config: &serde_json::Value,
22    _measurer: &dyn TextMeasurer,
23) -> Result<PacketDiagramLayout> {
24    let _ = (model.acc_title.as_deref(), model.acc_descr.as_deref());
25
26    fn config_bool(cfg: &Value, path: &[&str]) -> Option<bool> {
27        let mut cur = cfg;
28        for key in path {
29            cur = cur.get(*key)?;
30        }
31        cur.as_bool()
32    }
33
34    fn config_i64(cfg: &Value, path: &[&str]) -> Option<i64> {
35        let mut cur = cfg;
36        for key in path {
37            cur = cur.get(*key)?;
38        }
39        cur.as_i64()
40    }
41
42    // Mermaid 11.12.2 defaults (see `repo-ref/mermaid/.../packet/db.ts` + `DEFAULT_CONFIG.packet`).
43    let show_bits: bool = config_bool(effective_config, &["packet", "showBits"]).unwrap_or(true);
44    let row_height: f64 = config_f64(effective_config, &["packet", "rowHeight"])
45        .unwrap_or(32.0)
46        .max(1.0);
47    let padding_x: f64 = config_f64(effective_config, &["packet", "paddingX"])
48        .unwrap_or(5.0)
49        .max(0.0);
50    // Mermaid applies `showBits` after merging defaults+user config by bumping `paddingY` by +10.
51    // See `repo-ref/mermaid/packages/mermaid/src/diagrams/packet/db.ts`.
52    let mut padding_y: f64 = config_f64(effective_config, &["packet", "paddingY"])
53        .unwrap_or(5.0)
54        .max(0.0);
55    if show_bits {
56        padding_y += 10.0;
57    }
58    let bit_width: f64 = config_f64(effective_config, &["packet", "bitWidth"])
59        .unwrap_or(32.0)
60        .max(1.0);
61    let bits_per_row: i64 = config_i64(effective_config, &["packet", "bitsPerRow"])
62        .unwrap_or(32)
63        .max(1);
64
65    let total_row_height = row_height + padding_y;
66    let title_from_semantic = model
67        .title
68        .as_deref()
69        .map(str::trim)
70        .filter(|t| !t.is_empty());
71    let title_from_meta = diagram_title.map(str::trim).filter(|t| !t.is_empty());
72    let has_title = title_from_semantic.or(title_from_meta).is_some();
73
74    let words_count = model.packet.len();
75    let svg_height =
76        total_row_height * ((words_count + 1) as f64) - if has_title { 0.0 } else { row_height };
77    let svg_width = bit_width * (bits_per_row as f64) + 2.0;
78
79    let mut words: Vec<PacketWordLayout> = Vec::new();
80    for (row_number, word) in model.packet.iter().enumerate() {
81        let word_y = (row_number as f64) * total_row_height + padding_y;
82        let mut blocks: Vec<PacketBlockLayout> = Vec::new();
83        for block in word {
84            let block_x = ((block.start % bits_per_row) as f64) * bit_width + 1.0;
85            let width = ((block.end - block.start + 1) as f64) * bit_width - padding_x;
86            blocks.push(PacketBlockLayout {
87                start: block.start,
88                end: block.end,
89                label: block.label.clone(),
90                x: block_x,
91                y: word_y,
92                width,
93                height: row_height,
94            });
95        }
96        words.push(PacketWordLayout { blocks });
97    }
98
99    Ok(PacketDiagramLayout {
100        bounds: Some(Bounds {
101            min_x: 0.0,
102            min_y: 0.0,
103            max_x: svg_width,
104            max_y: svg_height.max(1.0),
105        }),
106        width: svg_width,
107        height: svg_height.max(1.0),
108        row_height,
109        padding_x,
110        padding_y,
111        bit_width,
112        bits_per_row,
113        show_bits,
114        words,
115    })
116}