1#![forbid(unsafe_code)]
2
3pub mod architecture;
10pub mod block;
11pub mod c4;
12pub mod class;
13mod entities;
14pub mod er;
15pub mod error;
16pub mod flowchart;
17pub mod gantt;
18mod generated;
19pub mod gitgraph;
20pub mod info;
21pub mod journey;
22mod json;
23pub mod kanban;
24pub mod mindmap;
25pub mod model;
26pub mod packet;
27pub mod pie;
28pub mod quadrantchart;
29pub mod radar;
30pub mod requirement;
31pub mod sankey;
32pub mod sequence;
33pub mod state;
34pub mod svg;
35pub mod text;
36pub mod timeline;
37pub mod treemap;
38mod trig_tables;
39pub mod xychart;
40
41use crate::model::{LayoutDiagram, LayoutMeta, LayoutedDiagram};
42use crate::text::{DeterministicTextMeasurer, TextMeasurer};
43use merman_core::{ParsedDiagram, ParsedDiagramRender, RenderSemanticModel};
44use serde_json::Value;
45use std::sync::Arc;
46
47#[derive(Debug, thiserror::Error)]
48pub enum Error {
49 #[error("unsupported diagram type for layout: {diagram_type}")]
50 UnsupportedDiagram { diagram_type: String },
51 #[error("invalid semantic model: {message}")]
52 InvalidModel { message: String },
53 #[error("semantic model JSON error: {0}")]
54 Json(#[from] serde_json::Error),
55}
56
57pub type Result<T> = std::result::Result<T, Error>;
58
59#[derive(Clone)]
60pub struct LayoutOptions {
61 pub text_measurer: Arc<dyn TextMeasurer + Send + Sync>,
62 pub viewport_width: f64,
63 pub viewport_height: f64,
64 pub use_manatee_layout: bool,
67}
68
69impl Default for LayoutOptions {
70 fn default() -> Self {
71 Self {
72 text_measurer: Arc::new(DeterministicTextMeasurer::default()),
73 viewport_width: 800.0,
74 viewport_height: 600.0,
75 use_manatee_layout: false,
76 }
77 }
78}
79
80impl LayoutOptions {
81 pub fn headless_svg_defaults() -> Self {
86 Self {
87 text_measurer: Arc::new(crate::text::VendoredFontMetricsTextMeasurer::default()),
88 use_manatee_layout: true,
91 ..Default::default()
92 }
93 }
94
95 pub fn with_text_measurer(mut self, measurer: Arc<dyn TextMeasurer + Send + Sync>) -> Self {
96 self.text_measurer = measurer;
97 self
98 }
99}
100
101pub fn layout_parsed(parsed: &ParsedDiagram, options: &LayoutOptions) -> Result<LayoutedDiagram> {
102 let meta = LayoutMeta::from_parse_metadata(&parsed.meta);
103 let layout = layout_parsed_layout_only(parsed, options)?;
104
105 Ok(LayoutedDiagram {
106 meta,
107 semantic: Value::clone(&parsed.model),
108 layout,
109 })
110}
111
112pub fn layout_parsed_layout_only(
113 parsed: &ParsedDiagram,
114 options: &LayoutOptions,
115) -> Result<LayoutDiagram> {
116 let diagram_type = parsed.meta.diagram_type.as_str();
117 let effective_config = parsed.meta.effective_config.as_value();
118 let title = parsed.meta.title.as_deref();
119
120 let layout = match diagram_type {
121 "error" => LayoutDiagram::ErrorDiagram(error::layout_error_diagram(
122 &parsed.model,
123 effective_config,
124 options.text_measurer.as_ref(),
125 )?),
126 "block" => LayoutDiagram::BlockDiagram(block::layout_block_diagram(
127 &parsed.model,
128 effective_config,
129 options.text_measurer.as_ref(),
130 )?),
131 "architecture" => {
132 LayoutDiagram::ArchitectureDiagram(architecture::layout_architecture_diagram(
133 &parsed.model,
134 effective_config,
135 options.text_measurer.as_ref(),
136 options.use_manatee_layout,
137 )?)
138 }
139 "requirement" => {
140 LayoutDiagram::RequirementDiagram(requirement::layout_requirement_diagram(
141 &parsed.model,
142 effective_config,
143 options.text_measurer.as_ref(),
144 )?)
145 }
146 "radar" => LayoutDiagram::RadarDiagram(radar::layout_radar_diagram(
147 &parsed.model,
148 effective_config,
149 options.text_measurer.as_ref(),
150 )?),
151 "treemap" => LayoutDiagram::TreemapDiagram(treemap::layout_treemap_diagram(
152 &parsed.model,
153 effective_config,
154 options.text_measurer.as_ref(),
155 )?),
156 "flowchart-v2" => LayoutDiagram::FlowchartV2(flowchart::layout_flowchart_v2(
157 &parsed.model,
158 effective_config,
159 options.text_measurer.as_ref(),
160 )?),
161 "stateDiagram" => LayoutDiagram::StateDiagramV2(state::layout_state_diagram_v2(
162 &parsed.model,
163 effective_config,
164 options.text_measurer.as_ref(),
165 )?),
166 "classDiagram" | "class" => LayoutDiagram::ClassDiagramV2(class::layout_class_diagram_v2(
167 &parsed.model,
168 effective_config,
169 options.text_measurer.as_ref(),
170 )?),
171 "er" | "erDiagram" => LayoutDiagram::ErDiagram(er::layout_er_diagram(
172 &parsed.model,
173 effective_config,
174 options.text_measurer.as_ref(),
175 )?),
176 "sequence" | "zenuml" => LayoutDiagram::SequenceDiagram(sequence::layout_sequence_diagram(
177 &parsed.model,
178 effective_config,
179 options.text_measurer.as_ref(),
180 )?),
181 "info" => LayoutDiagram::InfoDiagram(info::layout_info_diagram(
182 &parsed.model,
183 effective_config,
184 options.text_measurer.as_ref(),
185 )?),
186 "packet" => LayoutDiagram::PacketDiagram(packet::layout_packet_diagram(
187 &parsed.model,
188 title,
189 effective_config,
190 options.text_measurer.as_ref(),
191 )?),
192 "timeline" => LayoutDiagram::TimelineDiagram(timeline::layout_timeline_diagram(
193 &parsed.model,
194 effective_config,
195 options.text_measurer.as_ref(),
196 )?),
197 "gantt" => LayoutDiagram::GanttDiagram(gantt::layout_gantt_diagram(
198 &parsed.model,
199 effective_config,
200 options.text_measurer.as_ref(),
201 )?),
202 "c4" => LayoutDiagram::C4Diagram(c4::layout_c4_diagram(
203 &parsed.model,
204 effective_config,
205 options.text_measurer.as_ref(),
206 options.viewport_width,
207 options.viewport_height,
208 )?),
209 "journey" => LayoutDiagram::JourneyDiagram(journey::layout_journey_diagram(
210 &parsed.model,
211 effective_config,
212 options.text_measurer.as_ref(),
213 )?),
214 "gitGraph" => LayoutDiagram::GitGraphDiagram(gitgraph::layout_gitgraph_diagram(
215 &parsed.model,
216 effective_config,
217 options.text_measurer.as_ref(),
218 )?),
219 "kanban" => LayoutDiagram::KanbanDiagram(kanban::layout_kanban_diagram(
220 &parsed.model,
221 effective_config,
222 options.text_measurer.as_ref(),
223 )?),
224 "pie" => LayoutDiagram::PieDiagram(pie::layout_pie_diagram(
225 &parsed.model,
226 effective_config,
227 options.text_measurer.as_ref(),
228 )?),
229 "xychart" => LayoutDiagram::XyChartDiagram(xychart::layout_xychart_diagram(
230 &parsed.model,
231 effective_config,
232 options.text_measurer.as_ref(),
233 )?),
234 "quadrantChart" => {
235 LayoutDiagram::QuadrantChartDiagram(quadrantchart::layout_quadrantchart_diagram(
236 &parsed.model,
237 effective_config,
238 options.text_measurer.as_ref(),
239 )?)
240 }
241 "mindmap" => LayoutDiagram::MindmapDiagram(mindmap::layout_mindmap_diagram(
242 &parsed.model,
243 effective_config,
244 options.text_measurer.as_ref(),
245 options.use_manatee_layout,
246 )?),
247 "sankey" => LayoutDiagram::SankeyDiagram(sankey::layout_sankey_diagram(
248 &parsed.model,
249 effective_config,
250 options.text_measurer.as_ref(),
251 )?),
252 other => {
253 return Err(Error::UnsupportedDiagram {
254 diagram_type: other.to_string(),
255 });
256 }
257 };
258 Ok(layout)
259}
260
261pub fn layout_parsed_render_layout_only(
262 parsed: &ParsedDiagramRender,
263 options: &LayoutOptions,
264) -> Result<LayoutDiagram> {
265 let diagram_type = parsed.meta.diagram_type.as_str();
266 let effective_config = parsed.meta.effective_config.as_value();
267 let title = parsed.meta.title.as_deref();
268
269 match (&parsed.model, diagram_type) {
270 (RenderSemanticModel::Mindmap(model), "mindmap") => Ok(LayoutDiagram::MindmapDiagram(
271 mindmap::layout_mindmap_diagram_typed(
272 model,
273 effective_config,
274 options.text_measurer.as_ref(),
275 options.use_manatee_layout,
276 )?,
277 )),
278 (RenderSemanticModel::Architecture(model), "architecture") => Ok(
279 LayoutDiagram::ArchitectureDiagram(architecture::layout_architecture_diagram_typed(
280 model,
281 effective_config,
282 options.text_measurer.as_ref(),
283 options.use_manatee_layout,
284 )?),
285 ),
286 (RenderSemanticModel::Flowchart(model), "flowchart-v2" | "flowchart" | "flowchart-elk") => {
287 Ok(LayoutDiagram::FlowchartV2(
288 flowchart::layout_flowchart_v2_typed(
289 model,
290 effective_config,
291 options.text_measurer.as_ref(),
292 )?,
293 ))
294 }
295 (RenderSemanticModel::State(model), "stateDiagram" | "state") => Ok(
296 LayoutDiagram::StateDiagramV2(state::layout_state_diagram_v2_typed(
297 model,
298 effective_config,
299 options.text_measurer.as_ref(),
300 )?),
301 ),
302 (RenderSemanticModel::Class(model), "classDiagram" | "class") => Ok(
303 LayoutDiagram::ClassDiagramV2(class::layout_class_diagram_v2_typed(
304 model,
305 effective_config,
306 options.text_measurer.as_ref(),
307 )?),
308 ),
309 (RenderSemanticModel::Json(semantic), _) => {
310 let layout = match diagram_type {
311 "error" => LayoutDiagram::ErrorDiagram(error::layout_error_diagram(
312 semantic,
313 effective_config,
314 options.text_measurer.as_ref(),
315 )?),
316 "block" => LayoutDiagram::BlockDiagram(block::layout_block_diagram(
317 semantic,
318 effective_config,
319 options.text_measurer.as_ref(),
320 )?),
321 "architecture" => {
322 LayoutDiagram::ArchitectureDiagram(architecture::layout_architecture_diagram(
323 semantic,
324 effective_config,
325 options.text_measurer.as_ref(),
326 options.use_manatee_layout,
327 )?)
328 }
329 "requirement" => {
330 LayoutDiagram::RequirementDiagram(requirement::layout_requirement_diagram(
331 semantic,
332 effective_config,
333 options.text_measurer.as_ref(),
334 )?)
335 }
336 "radar" => LayoutDiagram::RadarDiagram(radar::layout_radar_diagram(
337 semantic,
338 effective_config,
339 options.text_measurer.as_ref(),
340 )?),
341 "treemap" => LayoutDiagram::TreemapDiagram(treemap::layout_treemap_diagram(
342 semantic,
343 effective_config,
344 options.text_measurer.as_ref(),
345 )?),
346 "flowchart-v2" => LayoutDiagram::FlowchartV2(flowchart::layout_flowchart_v2(
347 semantic,
348 effective_config,
349 options.text_measurer.as_ref(),
350 )?),
351 "stateDiagram" => LayoutDiagram::StateDiagramV2(state::layout_state_diagram_v2(
352 semantic,
353 effective_config,
354 options.text_measurer.as_ref(),
355 )?),
356 "classDiagram" | "class" => {
357 LayoutDiagram::ClassDiagramV2(class::layout_class_diagram_v2(
358 semantic,
359 effective_config,
360 options.text_measurer.as_ref(),
361 )?)
362 }
363 "er" | "erDiagram" => LayoutDiagram::ErDiagram(er::layout_er_diagram(
364 semantic,
365 effective_config,
366 options.text_measurer.as_ref(),
367 )?),
368 "sequence" | "zenuml" => {
369 LayoutDiagram::SequenceDiagram(sequence::layout_sequence_diagram(
370 semantic,
371 effective_config,
372 options.text_measurer.as_ref(),
373 )?)
374 }
375 "info" => LayoutDiagram::InfoDiagram(info::layout_info_diagram(
376 semantic,
377 effective_config,
378 options.text_measurer.as_ref(),
379 )?),
380 "packet" => LayoutDiagram::PacketDiagram(packet::layout_packet_diagram(
381 semantic,
382 title,
383 effective_config,
384 options.text_measurer.as_ref(),
385 )?),
386 "timeline" => LayoutDiagram::TimelineDiagram(timeline::layout_timeline_diagram(
387 semantic,
388 effective_config,
389 options.text_measurer.as_ref(),
390 )?),
391 "gantt" => LayoutDiagram::GanttDiagram(gantt::layout_gantt_diagram(
392 semantic,
393 effective_config,
394 options.text_measurer.as_ref(),
395 )?),
396 "c4" => LayoutDiagram::C4Diagram(c4::layout_c4_diagram(
397 semantic,
398 effective_config,
399 options.text_measurer.as_ref(),
400 options.viewport_width,
401 options.viewport_height,
402 )?),
403 "journey" => LayoutDiagram::JourneyDiagram(journey::layout_journey_diagram(
404 semantic,
405 effective_config,
406 options.text_measurer.as_ref(),
407 )?),
408 "gitGraph" => LayoutDiagram::GitGraphDiagram(gitgraph::layout_gitgraph_diagram(
409 semantic,
410 effective_config,
411 options.text_measurer.as_ref(),
412 )?),
413 "kanban" => LayoutDiagram::KanbanDiagram(kanban::layout_kanban_diagram(
414 semantic,
415 effective_config,
416 options.text_measurer.as_ref(),
417 )?),
418 "pie" => LayoutDiagram::PieDiagram(pie::layout_pie_diagram(
419 semantic,
420 effective_config,
421 options.text_measurer.as_ref(),
422 )?),
423 "xychart" => LayoutDiagram::XyChartDiagram(xychart::layout_xychart_diagram(
424 semantic,
425 effective_config,
426 options.text_measurer.as_ref(),
427 )?),
428 "quadrantChart" => LayoutDiagram::QuadrantChartDiagram(
429 quadrantchart::layout_quadrantchart_diagram(
430 semantic,
431 effective_config,
432 options.text_measurer.as_ref(),
433 )?,
434 ),
435 "mindmap" => LayoutDiagram::MindmapDiagram(mindmap::layout_mindmap_diagram(
436 semantic,
437 effective_config,
438 options.text_measurer.as_ref(),
439 options.use_manatee_layout,
440 )?),
441 "sankey" => LayoutDiagram::SankeyDiagram(sankey::layout_sankey_diagram(
442 semantic,
443 effective_config,
444 options.text_measurer.as_ref(),
445 )?),
446 other => {
447 return Err(Error::UnsupportedDiagram {
448 diagram_type: other.to_string(),
449 });
450 }
451 };
452 Ok(layout)
453 }
454 _ => Err(Error::InvalidModel {
455 message: format!("unexpected render model variant for diagram type: {diagram_type}"),
456 }),
457 }
458}