1use crate::Result;
2use crate::math::MathRenderer;
3use crate::model::{LayoutCluster, SequenceDiagramLayout};
4use crate::text::TextMeasurer;
5use merman_core::MermaidConfig;
6use merman_core::diagrams::sequence::SequenceDiagramRenderModel;
7use serde_json::Value;
8
9mod activation;
10mod actors;
11mod block_bounds;
12mod block_steps;
13pub(crate) mod config;
14mod constants;
15mod messages;
16mod metrics;
17mod notes;
18mod orchestration;
19mod rect;
20mod root_bounds;
21
22pub(crate) use activation::{sequence_activation_stack_bounds, sequence_activation_start_x};
23pub(crate) use constants::{
24 SEQUENCE_FRAME_GEOM_PAD_PX, SEQUENCE_FRAME_SIDE_PAD_PX, SEQUENCE_MESSAGE_WRAP_SLACK_FACTOR,
25 SEQUENCE_SELF_MESSAGE_FRAME_EXTRA_Y_PX, sequence_actor_popup_panel_height,
26 sequence_text_dimensions_height_px, sequence_text_line_step_px,
27};
28pub(crate) use metrics::{SequenceMathHeightMode, measure_sequence_math_label};
29pub(crate) use notes::sequence_note_final_wrapped_lines;
30
31use actors::{SequenceActorLayoutPlan, SequenceActorLayoutPlanContext, plan_sequence_actors};
32use block_bounds::sequence_block_bounds;
33use config::SequenceLayoutSettings;
34use orchestration::{SequenceLayoutGraph, SequenceLayoutGraphContext, build_sequence_layout_graph};
35use rect::sequence_rect_stack_x_bounds;
36use root_bounds::{SequenceRootBoundsContext, sequence_root_bounds};
37
38pub fn layout_sequence_diagram(
39 semantic: &Value,
40 effective_config: &Value,
41 measurer: &dyn TextMeasurer,
42 math_renderer: Option<&(dyn MathRenderer + Send + Sync)>,
43) -> Result<SequenceDiagramLayout> {
44 layout_sequence_diagram_with_title(semantic, None, effective_config, measurer, math_renderer)
45}
46
47pub fn layout_sequence_diagram_with_title(
48 semantic: &Value,
49 diagram_title: Option<&str>,
50 effective_config: &Value,
51 measurer: &dyn TextMeasurer,
52 math_renderer: Option<&(dyn MathRenderer + Send + Sync)>,
53) -> Result<SequenceDiagramLayout> {
54 let model: SequenceDiagramRenderModel = crate::json::from_value_ref(semantic)?;
55 layout_sequence_diagram_typed_with_title(
56 &model,
57 diagram_title,
58 effective_config,
59 measurer,
60 math_renderer,
61 )
62}
63
64pub fn layout_sequence_diagram_typed(
65 model: &SequenceDiagramRenderModel,
66 effective_config: &Value,
67 measurer: &dyn TextMeasurer,
68 math_renderer: Option<&(dyn MathRenderer + Send + Sync)>,
69) -> Result<SequenceDiagramLayout> {
70 layout_sequence_diagram_typed_with_title(model, None, effective_config, measurer, math_renderer)
71}
72
73pub fn layout_sequence_diagram_typed_with_title(
74 model: &SequenceDiagramRenderModel,
75 diagram_title: Option<&str>,
76 effective_config: &Value,
77 measurer: &dyn TextMeasurer,
78 math_renderer: Option<&(dyn MathRenderer + Send + Sync)>,
79) -> Result<SequenceDiagramLayout> {
80 let math_config = MermaidConfig::from_value(effective_config.clone());
81 let settings = SequenceLayoutSettings::from_effective_config(effective_config);
82
83 let SequenceActorLayoutPlan {
84 actor_index,
85 actor_widths,
86 actor_base_heights,
87 actor_box,
88 actor_left_x,
89 actor_centers_x,
90 box_margins,
91 actor_top_offset_y,
92 max_actor_layout_height,
93 has_boxes,
94 } = plan_sequence_actors(SequenceActorLayoutPlanContext {
95 model,
96 measurer,
97 actor_text_style: &settings.actor_text_style,
98 note_text_style: &settings.note_text_style,
99 msg_text_style: &settings.msg_text_style,
100 math_config: &math_config,
101 math_renderer,
102 actor_width_min: settings.sequence_default_width,
103 actor_height: settings.actor_height,
104 actor_margin: settings.actor_margin,
105 actor_font_size: settings.actor_text_style.font_size,
106 box_margin: settings.box_margin,
107 box_text_margin: settings.box_text_margin,
108 wrap_padding: settings.wrap_padding,
109 message_width_scale: settings.message_width_scale,
110 message_font_size: settings.msg_text_style.font_size,
111 })?;
112
113 let clusters: Vec<LayoutCluster> = Vec::new();
114
115 let SequenceLayoutGraph {
116 mut nodes,
117 edges,
118 bottom_box_top_y,
119 } = build_sequence_layout_graph(SequenceLayoutGraphContext {
120 model,
121 actor_index: &actor_index,
122 actor_centers_x: &actor_centers_x,
123 actor_widths: &actor_widths,
124 actor_base_heights: &actor_base_heights,
125 actor_top_offset_y,
126 max_actor_layout_height,
127 actor_width_min: settings.sequence_default_width,
128 sequence_default_width: settings.sequence_default_width,
129 actor_height: settings.actor_height,
130 message_margin: settings.message_margin,
131 box_margin: settings.box_margin,
132 box_text_margin: settings.box_text_margin,
133 bottom_margin_adj: settings.bottom_margin_adj,
134 label_box_height: settings.label_box_height,
135 message_step: settings.message_step,
136 message_text_line_height: settings.message_text_line_height,
137 msg_label_offset: settings.msg_label_offset,
138 message_font_size: settings.msg_text_style.font_size,
139 message_width_scale: settings.message_width_scale,
140 wrap_padding: settings.wrap_padding,
141 mirror_actors: settings.mirror_actors,
142 activation_width: settings.activation_width,
143 measurer,
144 msg_text_style: &settings.msg_text_style,
145 note_text_style: &settings.note_text_style,
146 math_config: &math_config,
147 math_renderer,
148 });
149
150 let block_bounds = sequence_block_bounds(model, &nodes, &edges);
155
156 let rect_x_bounds = sequence_rect_stack_x_bounds(
157 model,
158 &actor_index,
159 &actor_centers_x,
160 &edges,
161 &nodes,
162 settings.sequence_default_width,
163 settings.box_margin,
164 );
165 if !rect_x_bounds.is_empty() {
166 for n in &mut nodes {
167 let Some(start_id) = n.id.strip_prefix("rect-") else {
168 continue;
169 };
170 let Some((min_x, max_x)) = rect_x_bounds.get(start_id).copied() else {
171 continue;
172 };
173 n.x = (min_x + max_x) / 2.0;
174 n.width = (max_x - min_x).max(1.0);
175 }
176 }
177
178 let bounds = Some(sequence_root_bounds(SequenceRootBoundsContext {
179 model,
180 diagram_title,
181 nodes: &nodes,
182 edges: &edges,
183 block_bounds,
184 actor_index: &actor_index,
185 actor_centers_x: &actor_centers_x,
186 actor_left_x: &actor_left_x,
187 actor_widths: &actor_widths,
188 actor_box: &actor_box,
189 box_margins: &box_margins,
190 actor_width_min: settings.sequence_default_width,
191 actor_height: settings.actor_height,
192 bottom_box_top_y,
193 diagram_margin_x: settings.diagram_margin_x,
194 diagram_margin_y: settings.diagram_margin_y,
195 bottom_margin_adj: settings.bottom_margin_adj,
196 box_margin: settings.box_margin,
197 wrap_padding: settings.wrap_padding,
198 has_boxes,
199 mirror_actors: settings.mirror_actors,
200 measurer,
201 msg_text_style: &settings.msg_text_style,
202 note_text_style: &settings.note_text_style,
203 math_config: &math_config,
204 math_renderer,
205 }));
206
207 Ok(SequenceDiagramLayout {
208 nodes,
209 edges,
210 clusters,
211 bounds,
212 })
213}
214
215pub(crate) fn sequence_render_title<'a>(
216 model_title: Option<&'a str>,
217 diagram_title: Option<&'a str>,
218) -> Option<&'a str> {
219 if model_title.is_none_or(|t| t.trim().is_empty())
220 && let Some(title) = diagram_title.map(str::trim).filter(|t| !t.is_empty())
221 {
222 return Some(title);
223 }
224 model_title
225}