Skip to main content

merman_render/
model.rs

1#[cfg(any(feature = "diagram-class", feature = "diagram-sequence"))]
2use rustc_hash::FxHashMap;
3use serde::{Deserialize, Serialize};
4#[cfg(feature = "diagram-class")]
5use std::sync::Arc;
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct Bounds {
9    pub min_x: f64,
10    pub min_y: f64,
11    pub max_x: f64,
12    pub max_y: f64,
13}
14
15impl Bounds {
16    pub fn from_points(points: impl IntoIterator<Item = (f64, f64)>) -> Option<Self> {
17        let mut it = points.into_iter();
18        let (x0, y0) = it.next()?;
19        let mut b = Self {
20            min_x: x0,
21            min_y: y0,
22            max_x: x0,
23            max_y: y0,
24        };
25        for (x, y) in it {
26            b.min_x = b.min_x.min(x);
27            b.min_y = b.min_y.min(y);
28            b.max_x = b.max_x.max(x);
29            b.max_y = b.max_y.max(y);
30        }
31        Some(b)
32    }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct LayoutPoint {
37    pub x: f64,
38    pub y: f64,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct LayoutLabel {
43    pub x: f64,
44    pub y: f64,
45    pub width: f64,
46    pub height: f64,
47}
48
49#[derive(Debug, Clone)]
50#[cfg(feature = "diagram-class")]
51pub struct ClassNodeRowMetrics {
52    pub members: Vec<crate::text::TextMetrics>,
53    pub methods: Vec<crate::text::TextMetrics>,
54}
55
56#[derive(Debug, Clone)]
57#[cfg(feature = "diagram-class")]
58pub struct ClassPreparedHtmlLabel {
59    pub metrics: crate::text::TextMetrics,
60    pub max_width_px: i64,
61    pub xhtml: String,
62}
63
64#[derive(Debug, Clone)]
65#[cfg(feature = "diagram-class")]
66pub struct ClassPreparedHtmlNodeLabels {
67    pub title: ClassPreparedHtmlLabel,
68    pub annotation: Option<ClassPreparedHtmlLabel>,
69    pub members: Vec<ClassPreparedHtmlLabel>,
70    pub methods: Vec<ClassPreparedHtmlLabel>,
71}
72
73#[derive(Debug, Clone)]
74#[cfg(feature = "diagram-class")]
75pub enum ClassNodeLabelPlan {
76    RowMetrics(ClassNodeRowMetrics),
77    PreparedHtml(ClassPreparedHtmlNodeLabels),
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct LayoutNode {
82    pub id: String,
83    pub x: f64,
84    pub y: f64,
85    pub width: f64,
86    pub height: f64,
87    pub is_cluster: bool,
88    #[serde(skip)]
89    pub label_width: Option<f64>,
90    #[serde(skip)]
91    pub label_height: Option<f64>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct LayoutCluster {
96    pub id: String,
97    pub x: f64,
98    pub y: f64,
99    pub width: f64,
100    pub height: f64,
101    /// Mermaid cluster "diff" value used during cluster positioning.
102    pub diff: f64,
103    /// Mermaid cluster "offsetY" value: title bbox height minus half padding.
104    pub offset_y: f64,
105    pub title: String,
106    pub title_label: LayoutLabel,
107    pub requested_dir: Option<String>,
108    pub effective_dir: String,
109    pub padding: f64,
110    pub title_margin_top: f64,
111    pub title_margin_bottom: f64,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct LayoutEdge {
116    pub id: String,
117    pub from: String,
118    pub to: String,
119    #[serde(default)]
120    pub from_cluster: Option<String>,
121    #[serde(default)]
122    pub to_cluster: Option<String>,
123    pub points: Vec<LayoutPoint>,
124    pub label: Option<LayoutLabel>,
125    #[serde(default)]
126    pub start_label_left: Option<LayoutLabel>,
127    #[serde(default)]
128    pub start_label_right: Option<LayoutLabel>,
129    #[serde(default)]
130    pub end_label_left: Option<LayoutLabel>,
131    #[serde(default)]
132    pub end_label_right: Option<LayoutLabel>,
133    /// Optional SVG marker id for marker-start (e.g. ER cardinality markers).
134    #[serde(default)]
135    pub start_marker: Option<String>,
136    /// Optional SVG marker id for marker-end (e.g. ER cardinality markers).
137    #[serde(default)]
138    pub end_marker: Option<String>,
139    /// Optional SVG dash pattern (e.g. identifying vs non-identifying ER relationships).
140    #[serde(default)]
141    pub stroke_dasharray: Option<String>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
145#[cfg(feature = "diagram-block")]
146pub struct BlockDiagramLayout {
147    pub nodes: Vec<LayoutNode>,
148    pub edges: Vec<LayoutEdge>,
149    #[serde(
150        default,
151        rename = "shapeGeometries",
152        skip_serializing_if = "Vec::is_empty"
153    )]
154    pub shape_geometries: Vec<crate::block::BlockShapeGeometry>,
155    pub bounds: Option<Bounds>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
159#[cfg(feature = "diagram-requirement")]
160pub struct RequirementDiagramLayout {
161    pub nodes: Vec<LayoutNode>,
162    pub edges: Vec<LayoutEdge>,
163    pub bounds: Option<Bounds>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167#[cfg(feature = "diagram-architecture")]
168pub struct ArchitectureDiagramLayout {
169    pub nodes: Vec<LayoutNode>,
170    pub edges: Vec<LayoutEdge>,
171    /// Local Cytoscape child contribution phases for Architecture services.
172    ///
173    /// These bounds are exposed for source-backed group/content audits. SVG renderers should use
174    /// their own phase-specific logic instead of treating this as a generic root-bounds source.
175    #[serde(default, skip_serializing_if = "Vec::is_empty")]
176    pub cytoscape_service_bounds: Vec<ArchitectureCytoscapeServiceBounds>,
177    /// Final FCoSE compound rectangles exposed for source-backed Architecture audits.
178    ///
179    /// Renderers should not treat these as Mermaid SVG group rects without a phase-specific
180    /// source audit; they are layout-engine rects, not browser `node.boundingBox()` values.
181    #[serde(default, skip_serializing_if = "Vec::is_empty")]
182    pub fcose_compound_bounds: Vec<ArchitectureCompoundBounds>,
183    pub bounds: Option<Bounds>,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
187#[cfg(feature = "diagram-architecture")]
188pub struct ArchitectureCompoundBounds {
189    pub id: String,
190    pub bounds: Bounds,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194#[cfg(feature = "diagram-architecture")]
195pub struct ArchitectureCytoscapeServiceBounds {
196    pub id: String,
197    #[serde(default, skip_serializing_if = "Option::is_none")]
198    pub in_group: Option<String>,
199    pub body_bounds: Bounds,
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub label_bounds: Option<Bounds>,
202    #[serde(default, skip_serializing_if = "Option::is_none")]
203    pub label_metrics: Option<ArchitectureCytoscapeServiceLabelMetrics>,
204    pub union_bounds: Bounds,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
208#[cfg(feature = "diagram-architecture")]
209pub struct ArchitectureCytoscapeServiceLabelMetrics {
210    pub text_width: f64,
211    pub half_width: f64,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
215#[cfg(feature = "diagram-mindmap")]
216pub struct MindmapDiagramLayout {
217    pub nodes: Vec<LayoutNode>,
218    pub edges: Vec<LayoutEdge>,
219    pub bounds: Option<Bounds>,
220    /// Swimlane decoration owned by the selected renderer, not semantic Mindmap nodes.
221    #[serde(skip)]
222    pub swimlane_lanes: Vec<LayoutCluster>,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
226#[cfg(feature = "diagram-sankey")]
227pub struct SankeyNodeLayout {
228    pub id: String,
229    pub index: usize,
230    pub depth: usize,
231    pub height: usize,
232    pub layer: usize,
233    pub value: f64,
234    pub x0: f64,
235    pub x1: f64,
236    pub y0: f64,
237    pub y1: f64,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241#[cfg(feature = "diagram-sankey")]
242pub struct SankeyLinkLayout {
243    pub index: usize,
244    pub source: String,
245    pub target: String,
246    pub value: f64,
247    pub width: f64,
248    pub y0: f64,
249    pub y1: f64,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
253#[cfg(feature = "diagram-sankey")]
254pub struct SankeyDiagramLayout {
255    pub bounds: Option<Bounds>,
256    pub width: f64,
257    pub height: f64,
258    pub node_width: f64,
259    pub node_padding: f64,
260    pub nodes: Vec<SankeyNodeLayout>,
261    pub links: Vec<SankeyLinkLayout>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265#[cfg(feature = "diagram-radar")]
266pub struct RadarAxisLayout {
267    pub label: String,
268    pub angle: f64,
269    pub line_x2: f64,
270    pub line_y2: f64,
271    pub label_x: f64,
272    pub label_y: f64,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
276#[cfg(feature = "diagram-radar")]
277pub struct RadarGraticuleShapeLayout {
278    pub kind: String,
279    pub r: Option<f64>,
280    #[serde(default)]
281    pub points: Vec<LayoutPoint>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285#[cfg(feature = "diagram-radar")]
286pub struct RadarCurveLayout {
287    pub label: String,
288    pub class_index: i64,
289    #[serde(default)]
290    pub points: Vec<LayoutPoint>,
291    pub path_d: String,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
295#[cfg(feature = "diagram-radar")]
296pub struct RadarLegendItemLayout {
297    pub label: String,
298    pub class_index: i64,
299    pub x: f64,
300    pub y: f64,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304#[cfg(feature = "diagram-radar")]
305pub struct RadarDiagramLayout {
306    pub bounds: Option<Bounds>,
307    pub svg_width: f64,
308    pub svg_height: f64,
309    pub center_x: f64,
310    pub center_y: f64,
311    pub radius: f64,
312    pub axis_label_factor: f64,
313    pub title_y: f64,
314    #[serde(default)]
315    pub axes: Vec<RadarAxisLayout>,
316    #[serde(default)]
317    pub graticules: Vec<RadarGraticuleShapeLayout>,
318    #[serde(default)]
319    pub curves: Vec<RadarCurveLayout>,
320    #[serde(default)]
321    pub legend_items: Vec<RadarLegendItemLayout>,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
325#[cfg(feature = "diagram-treemap")]
326pub struct TreemapSectionLayout {
327    pub name: String,
328    pub depth: i64,
329    pub value: f64,
330    pub x0: f64,
331    pub y0: f64,
332    pub x1: f64,
333    pub y1: f64,
334    #[serde(default)]
335    pub class_selector: Option<String>,
336    #[serde(default)]
337    pub css_compiled_styles: Option<Vec<String>>,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
341#[cfg(feature = "diagram-treemap")]
342pub struct TreemapLeafLayout {
343    pub name: String,
344    pub value: f64,
345    #[serde(default)]
346    pub parent_name: Option<String>,
347    pub x0: f64,
348    pub y0: f64,
349    pub x1: f64,
350    pub y1: f64,
351    #[serde(default)]
352    pub class_selector: Option<String>,
353    #[serde(default)]
354    pub css_compiled_styles: Option<Vec<String>>,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
358#[cfg(feature = "diagram-treemap")]
359pub struct TreemapDiagramLayout {
360    pub title_height: f64,
361    pub width: f64,
362    pub height: f64,
363    pub use_max_width: bool,
364    pub diagram_padding: f64,
365    pub show_values: bool,
366    pub value_format: String,
367    #[serde(default, rename = "accTitle")]
368    pub acc_title: Option<String>,
369    #[serde(default, rename = "accDescr")]
370    pub acc_descr: Option<String>,
371    #[serde(default)]
372    pub title: Option<String>,
373    #[serde(default)]
374    pub sections: Vec<TreemapSectionLayout>,
375    #[serde(default)]
376    pub leaves: Vec<TreemapLeafLayout>,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
380#[cfg(feature = "diagram-venn")]
381pub struct VennCircleLayout {
382    pub set: String,
383    pub x: f64,
384    pub y: f64,
385    pub radius: f64,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
389#[cfg(feature = "diagram-venn")]
390pub struct VennAreaLayout {
391    pub sets: Vec<String>,
392    pub size: f64,
393    #[serde(default, skip_serializing_if = "Option::is_none")]
394    pub label: Option<String>,
395    pub text_x: f64,
396    pub text_y: f64,
397    pub text_disjoint: bool,
398    #[serde(default)]
399    pub circles: Vec<VennCircleLayout>,
400    pub path: String,
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
404#[cfg(feature = "diagram-venn")]
405pub struct VennTextDebugCellLayout {
406    pub x: f64,
407    pub y: f64,
408    pub width: f64,
409    pub height: f64,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
413#[cfg(feature = "diagram-venn")]
414pub struct VennTextAreaLayout {
415    pub sets: Vec<String>,
416    pub center_x: f64,
417    pub center_y: f64,
418    pub inner_radius: f64,
419    pub font_size: f64,
420    #[serde(default)]
421    pub debug_cells: Vec<VennTextDebugCellLayout>,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
425#[cfg(feature = "diagram-venn")]
426pub struct VennTextNodeLayout {
427    pub sets: Vec<String>,
428    pub id: String,
429    #[serde(default, skip_serializing_if = "Option::is_none")]
430    pub label: Option<String>,
431    pub x: f64,
432    pub y: f64,
433    pub width: f64,
434    pub height: f64,
435}
436
437#[derive(Debug, Clone, Serialize, Deserialize)]
438#[cfg(feature = "diagram-venn")]
439pub struct VennDiagramLayout {
440    pub bounds: Option<Bounds>,
441    pub width: f64,
442    pub height: f64,
443    pub diagram_height: f64,
444    pub title_height: f64,
445    pub scale: f64,
446    pub padding: f64,
447    pub use_max_width: bool,
448    pub use_debug_layout: bool,
449    #[serde(default)]
450    pub areas: Vec<VennAreaLayout>,
451    #[serde(default)]
452    pub text_areas: Vec<VennTextAreaLayout>,
453    #[serde(default)]
454    pub text_nodes: Vec<VennTextNodeLayout>,
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
458#[cfg(feature = "diagram-xychart")]
459pub struct XyChartRectData {
460    pub x: f64,
461    pub y: f64,
462    pub width: f64,
463    pub height: f64,
464    pub fill: String,
465    #[serde(rename = "strokeFill")]
466    pub stroke_fill: String,
467    #[serde(rename = "strokeWidth")]
468    pub stroke_width: f64,
469}
470
471#[derive(Debug, Clone, Serialize, Deserialize)]
472#[cfg(feature = "diagram-xychart")]
473pub struct XyChartTextData {
474    pub text: String,
475    pub x: f64,
476    pub y: f64,
477    pub fill: String,
478    #[serde(rename = "fontSize")]
479    pub font_size: f64,
480    #[serde(default)]
481    pub rotation: f64,
482    #[serde(rename = "verticalPos")]
483    pub vertical_pos: String,
484    #[serde(rename = "horizontalPos")]
485    pub horizontal_pos: String,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize)]
489#[cfg(feature = "diagram-xychart")]
490pub struct XyChartPathData {
491    pub path: String,
492    #[serde(default)]
493    pub fill: Option<String>,
494    #[serde(rename = "strokeFill")]
495    pub stroke_fill: String,
496    #[serde(rename = "strokeWidth")]
497    pub stroke_width: f64,
498}
499
500#[derive(Debug, Clone, Serialize, Deserialize)]
501#[serde(tag = "type")]
502#[cfg(feature = "diagram-xychart")]
503pub enum XyChartDrawableElem {
504    #[serde(rename = "rect")]
505    Rect {
506        #[serde(rename = "groupTexts")]
507        group_texts: Vec<String>,
508        data: Vec<XyChartRectData>,
509    },
510    #[serde(rename = "text")]
511    Text {
512        #[serde(rename = "groupTexts")]
513        group_texts: Vec<String>,
514        data: Vec<XyChartTextData>,
515    },
516    #[serde(rename = "path")]
517    Path {
518        #[serde(rename = "groupTexts")]
519        group_texts: Vec<String>,
520        data: Vec<XyChartPathData>,
521    },
522}
523
524#[derive(Debug, Clone, Serialize, Deserialize)]
525#[cfg(feature = "diagram-xychart")]
526pub struct XyChartDiagramLayout {
527    pub width: f64,
528    pub height: f64,
529    #[serde(rename = "chartOrientation")]
530    pub chart_orientation: String,
531    #[serde(rename = "showDataLabel")]
532    pub show_data_label: bool,
533    #[serde(rename = "showDataLabelOutsideBar", default)]
534    pub show_data_label_outside_bar: bool,
535    #[serde(rename = "backgroundColor")]
536    pub background_color: String,
537    #[serde(rename = "labelData")]
538    pub label_data: Vec<String>,
539    #[serde(default)]
540    pub drawables: Vec<XyChartDrawableElem>,
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize)]
544#[cfg(feature = "diagram-quadrant-chart")]
545pub struct QuadrantChartTextData {
546    pub text: String,
547    pub x: f64,
548    pub y: f64,
549    pub fill: String,
550    #[serde(rename = "fontSize")]
551    pub font_size: f64,
552    #[serde(default)]
553    pub rotation: f64,
554    #[serde(rename = "verticalPos")]
555    pub vertical_pos: String,
556    #[serde(rename = "horizontalPos")]
557    pub horizontal_pos: String,
558}
559
560#[cfg(feature = "diagram-quadrant-chart")]
561pub type QuadrantChartAxisLabelData = QuadrantChartTextData;
562
563#[derive(Debug, Clone, Serialize, Deserialize)]
564#[cfg(feature = "diagram-quadrant-chart")]
565pub struct QuadrantChartQuadrantData {
566    pub x: f64,
567    pub y: f64,
568    pub width: f64,
569    pub height: f64,
570    pub fill: String,
571    pub text: QuadrantChartTextData,
572}
573
574#[derive(Debug, Clone, Serialize, Deserialize)]
575#[cfg(feature = "diagram-quadrant-chart")]
576pub struct QuadrantChartBorderLineData {
577    #[serde(rename = "strokeWidth")]
578    pub stroke_width: f64,
579    #[serde(rename = "strokeFill")]
580    pub stroke_fill: String,
581    pub x1: f64,
582    pub y1: f64,
583    pub x2: f64,
584    pub y2: f64,
585}
586
587#[derive(Debug, Clone, Serialize, Deserialize)]
588#[cfg(feature = "diagram-quadrant-chart")]
589pub struct QuadrantChartPointData {
590    pub x: f64,
591    pub y: f64,
592    pub fill: String,
593    pub radius: f64,
594    #[serde(rename = "strokeColor")]
595    pub stroke_color: String,
596    #[serde(rename = "strokeWidth")]
597    pub stroke_width: String,
598    pub text: QuadrantChartTextData,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
602#[cfg(feature = "diagram-quadrant-chart")]
603pub struct QuadrantChartDiagramLayout {
604    pub width: f64,
605    pub height: f64,
606    #[serde(default)]
607    pub title: Option<QuadrantChartTextData>,
608    pub quadrants: Vec<QuadrantChartQuadrantData>,
609    #[serde(rename = "borderLines")]
610    pub border_lines: Vec<QuadrantChartBorderLineData>,
611    pub points: Vec<QuadrantChartPointData>,
612    #[serde(rename = "axisLabels")]
613    pub axis_labels: Vec<QuadrantChartAxisLabelData>,
614}
615
616#[derive(Debug, Clone, Serialize, Deserialize)]
617#[cfg(any(
618    feature = "diagram-flowchart",
619    feature = "diagram-swimlane",
620    feature = "diagram-agentflow"
621))]
622pub struct FlowchartLayout {
623    pub nodes: Vec<LayoutNode>,
624    pub edges: Vec<LayoutEdge>,
625    pub clusters: Vec<LayoutCluster>,
626    pub bounds: Option<Bounds>,
627    /// Mermaid's DOM insertion order for each extracted root graph (`""` = top-level root).
628    #[serde(skip)]
629    pub dom_node_order_by_root: std::collections::HashMap<String, Vec<String>>,
630    /// Whether SVG emission follows Mermaid's ELK adapter DOM structure.
631    #[serde(skip)]
632    pub uses_elk_adapter_dom: bool,
633}
634
635#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
636#[cfg(any(
637    feature = "diagram-flowchart",
638    feature = "diagram-swimlane",
639    feature = "diagram-agentflow",
640    feature = "diagram-mindmap"
641))]
642pub enum SwimlaneDirection {
643    #[serde(rename = "TB")]
644    Tb,
645    #[serde(rename = "LR")]
646    Lr,
647    #[serde(rename = "BT")]
648    Bt,
649    #[serde(rename = "RL")]
650    Rl,
651}
652
653#[cfg(any(
654    feature = "diagram-flowchart",
655    feature = "diagram-swimlane",
656    feature = "diagram-agentflow",
657    feature = "diagram-mindmap"
658))]
659impl SwimlaneDirection {
660    pub const fn as_str(self) -> &'static str {
661        match self {
662            Self::Tb => "TB",
663            Self::Lr => "LR",
664            Self::Bt => "BT",
665            Self::Rl => "RL",
666        }
667    }
668}
669
670#[derive(Debug, Clone, Serialize, Deserialize)]
671#[cfg(any(
672    feature = "diagram-flowchart",
673    feature = "diagram-swimlane",
674    feature = "diagram-agentflow",
675    feature = "diagram-mindmap"
676))]
677pub struct SwimlaneTitleRect {
678    pub left: f64,
679    pub right: f64,
680    pub top: f64,
681    pub bottom: f64,
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize)]
685#[cfg(any(
686    feature = "diagram-flowchart",
687    feature = "diagram-swimlane",
688    feature = "diagram-agentflow",
689    feature = "diagram-mindmap"
690))]
691pub struct SwimlaneNodeLayout {
692    pub id: String,
693    pub label: String,
694    pub label_type: String,
695    pub shape: String,
696    #[serde(default)]
697    pub parent_id: Option<String>,
698    #[serde(default)]
699    pub top_lane_id: Option<String>,
700    pub x: f64,
701    pub y: f64,
702    pub width: f64,
703    pub height: f64,
704    pub label_width: f64,
705    pub label_height: f64,
706    pub layer: usize,
707    pub order: usize,
708    pub is_edge_label: bool,
709}
710
711#[derive(Debug, Clone, Serialize, Deserialize)]
712#[cfg(any(
713    feature = "diagram-flowchart",
714    feature = "diagram-swimlane",
715    feature = "diagram-agentflow",
716    feature = "diagram-mindmap"
717))]
718pub struct SwimlaneLaneLayout {
719    pub id: String,
720    pub title: String,
721    #[serde(default)]
722    pub parent_id: Option<String>,
723    pub x: f64,
724    pub y: f64,
725    pub width: f64,
726    pub height: f64,
727    pub padding: f64,
728    /// Layout-phase label width. SVG rendering must remeasure the emitted title with its
729    /// `SvgBBox` measurer rather than treating this as a browser `<text>.getBBox()` fact.
730    pub title_label_width: f64,
731    /// Layout-phase label height; see `title_label_width` for the phase boundary.
732    pub title_label_height: f64,
733    #[serde(default)]
734    pub content_top: Option<f64>,
735    #[serde(default)]
736    pub title_rect: Option<SwimlaneTitleRect>,
737    #[serde(default)]
738    pub requested_dir: Option<String>,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize)]
742#[cfg(any(
743    feature = "diagram-flowchart",
744    feature = "diagram-swimlane",
745    feature = "diagram-agentflow",
746    feature = "diagram-mindmap"
747))]
748pub struct SwimlaneEdgeLayout {
749    pub id: String,
750    pub from: String,
751    pub to: String,
752    pub points: Vec<LayoutPoint>,
753    #[serde(default)]
754    pub label_node_id: Option<String>,
755    pub reversed_for_layout: bool,
756    pub curve: String,
757}
758
759#[derive(Debug, Clone, Serialize, Deserialize)]
760#[cfg(any(
761    feature = "diagram-flowchart",
762    feature = "diagram-swimlane",
763    feature = "diagram-agentflow",
764    feature = "diagram-mindmap"
765))]
766pub struct SwimlaneLayout {
767    pub direction: SwimlaneDirection,
768    pub nodes: Vec<SwimlaneNodeLayout>,
769    pub lanes: Vec<SwimlaneLaneLayout>,
770    pub edges: Vec<SwimlaneEdgeLayout>,
771    pub bounds: Option<Bounds>,
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
775#[cfg(feature = "diagram-state")]
776pub struct StateDiagramLayout {
777    pub nodes: Vec<LayoutNode>,
778    pub edges: Vec<LayoutEdge>,
779    pub clusters: Vec<LayoutCluster>,
780    pub bounds: Option<Bounds>,
781    /// Registered ELK layout uses its own edge painting projection.
782    #[serde(skip)]
783    pub uses_elk_adapter_dom: bool,
784    #[serde(skip)]
785    pub elk_edge_paths: std::collections::HashMap<String, Vec<LayoutPoint>>,
786}
787
788#[derive(Debug, Clone, Serialize, Deserialize)]
789#[cfg(feature = "diagram-class")]
790pub struct ClassDiagramLayout {
791    pub nodes: Vec<LayoutNode>,
792    pub edges: Vec<LayoutEdge>,
793    pub clusters: Vec<LayoutCluster>,
794    pub bounds: Option<Bounds>,
795    /// Whether SVG emission follows Mermaid's registered ELK renderer DOM structure.
796    #[serde(skip)]
797    pub uses_elk_adapter_dom: bool,
798    #[serde(skip)]
799    pub class_label_plans_by_id: FxHashMap<String, Arc<ClassNodeLabelPlan>>,
800    /// DOM ownership produced by the same recursive Dagre preparation that owns layout.
801    ///
802    /// Compatibility layout JSON remains flat, while SVG rendering consumes this typed tree so it
803    /// never has to reconstruct extracted namespace roots from the semantic model.
804    #[serde(skip)]
805    pub render_tree: ClassRenderTree,
806}
807
808#[derive(Debug, Clone, Default)]
809#[cfg(feature = "diagram-class")]
810pub struct ClassRenderRoot {
811    pub namespace_id: Option<String>,
812    pub cluster_ids: Vec<String>,
813    pub edge_ids: Vec<String>,
814    pub items: Vec<ClassRenderItem>,
815}
816
817#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
818#[cfg(feature = "diagram-class")]
819pub struct ClassRenderRootId(pub usize);
820
821#[derive(Debug, Clone)]
822#[cfg(feature = "diagram-class")]
823pub struct ClassRenderTree {
824    pub roots: Vec<ClassRenderRoot>,
825    pub top: ClassRenderRootId,
826}
827
828#[cfg(feature = "diagram-class")]
829impl Default for ClassRenderTree {
830    fn default() -> Self {
831        Self {
832            roots: vec![ClassRenderRoot::default()],
833            top: ClassRenderRootId(0),
834        }
835    }
836}
837
838#[derive(Debug, Clone)]
839#[cfg(feature = "diagram-class")]
840pub enum ClassRenderItem {
841    Node(String),
842    Subgraph(ClassRenderRootId),
843}
844
845#[derive(Debug, Clone, Serialize, Deserialize)]
846#[cfg(feature = "diagram-er")]
847pub struct ErDiagramLayout {
848    pub nodes: Vec<LayoutNode>,
849    pub edges: Vec<LayoutEdge>,
850    /// SVG-facing edge projection.
851    ///
852    /// Dagre needs self-loop helper nodes and three internal edge segments to produce stable
853    /// ranks. Mermaid's common painter merges those segments back into one visible edge. Keep
854    /// that projection beside the canonical layout without serializing it into layout artifacts.
855    #[serde(default, skip)]
856    pub(crate) render_edges: Vec<LayoutEdge>,
857    #[serde(default, skip_serializing_if = "Vec::is_empty")]
858    pub clusters: Vec<LayoutCluster>,
859    pub bounds: Option<Bounds>,
860}
861
862#[derive(Debug, Clone)]
863#[cfg(feature = "diagram-sequence")]
864pub struct SequenceBlockLayout {
865    pub start_y: f64,
866    pub stop_y: f64,
867    /// Mermaid expands every active sequence frame by its nesting depth.
868    /// These coordinates are captured during layout replay rather than inferred from SVG nodes.
869    pub start_x: Option<f64>,
870    pub stop_x: Option<f64>,
871    pub section_ys_by_id: FxHashMap<String, f64>,
872}
873
874#[derive(Debug, Clone, Serialize, Deserialize)]
875#[cfg(feature = "diagram-sequence")]
876pub struct SequenceDiagramLayout {
877    pub nodes: Vec<LayoutNode>,
878    pub edges: Vec<LayoutEdge>,
879    pub clusters: Vec<LayoutCluster>,
880    pub bounds: Option<Bounds>,
881    /// Canonical vertical geometry captured while replaying Mermaid's sequence layout cursor.
882    ///
883    /// This remains outside compatibility layout JSON because it is renderer-owned phase data.
884    #[serde(skip)]
885    pub block_layouts_by_id: FxHashMap<String, SequenceBlockLayout>,
886}
887
888#[derive(Debug, Clone, Serialize, Deserialize)]
889#[cfg(feature = "diagram-info")]
890pub struct InfoDiagramLayout {
891    pub bounds: Option<Bounds>,
892    pub version: String,
893}
894
895#[derive(Debug, Clone, Serialize, Deserialize)]
896#[cfg(feature = "diagram-packet")]
897pub struct PacketBlockLayout {
898    pub start: i64,
899    pub end: i64,
900    pub label: String,
901    pub x: f64,
902    pub y: f64,
903    pub width: f64,
904    pub height: f64,
905}
906
907#[derive(Debug, Clone, Serialize, Deserialize)]
908#[cfg(feature = "diagram-packet")]
909pub struct PacketWordLayout {
910    pub blocks: Vec<PacketBlockLayout>,
911}
912
913#[derive(Debug, Clone, Serialize, Deserialize)]
914#[cfg(feature = "diagram-packet")]
915pub struct PacketDiagramLayout {
916    pub bounds: Option<Bounds>,
917    pub width: f64,
918    pub height: f64,
919    pub row_height: f64,
920    pub padding_x: f64,
921    pub padding_y: f64,
922    pub bit_width: f64,
923    pub bits_per_row: i64,
924    pub show_bits: bool,
925    pub words: Vec<PacketWordLayout>,
926}
927
928#[derive(Debug, Clone, Serialize, Deserialize)]
929#[cfg(feature = "diagram-pie")]
930pub struct PieSliceLayout {
931    pub label: String,
932    pub value: f64,
933    pub start_angle: f64,
934    pub end_angle: f64,
935    pub is_full_circle: bool,
936    pub percent: i64,
937    pub text_x: f64,
938    pub text_y: f64,
939    pub fill: String,
940}
941
942#[derive(Debug, Clone, Serialize, Deserialize)]
943#[cfg(feature = "diagram-pie")]
944pub struct PieLegendItemLayout {
945    pub label: String,
946    pub value: f64,
947    pub fill: String,
948    pub y: f64,
949}
950
951#[derive(Debug, Clone, Serialize, Deserialize)]
952#[cfg(feature = "diagram-pie")]
953pub struct PieDiagramLayout {
954    pub bounds: Option<Bounds>,
955    #[serde(default)]
956    pub title: Option<String>,
957    pub center_x: f64,
958    pub center_y: f64,
959    pub radius: f64,
960    pub outer_radius: f64,
961    pub legend_x: f64,
962    pub legend_start_y: f64,
963    pub legend_step_y: f64,
964    pub slices: Vec<PieSliceLayout>,
965    pub legend_items: Vec<PieLegendItemLayout>,
966}
967
968#[derive(Debug, Clone, Serialize, Deserialize)]
969#[cfg(feature = "diagram-timeline")]
970pub struct TimelineNodeLayout {
971    pub x: f64,
972    pub y: f64,
973    pub width: f64,
974    pub height: f64,
975    /// Width used for wrapping (excluding padding).
976    pub content_width: f64,
977    /// Padding used to compute `width` (Mermaid 11.12.2 uses 20).
978    pub padding: f64,
979    pub section_class: String,
980    pub label: String,
981    /// Wrapped lines as rendered into `<tspan>` nodes.
982    pub label_lines: Vec<String>,
983    pub kind: String,
984}
985
986#[derive(Debug, Clone, Serialize, Deserialize)]
987#[cfg(feature = "diagram-timeline")]
988pub struct TimelineLineLayout {
989    pub kind: String,
990    pub x1: f64,
991    pub y1: f64,
992    pub x2: f64,
993    pub y2: f64,
994}
995
996#[derive(Debug, Clone, Serialize, Deserialize)]
997#[cfg(feature = "diagram-timeline")]
998pub struct TimelineTaskLayout {
999    pub node: TimelineNodeLayout,
1000    #[serde(default)]
1001    pub connectors: Vec<TimelineLineLayout>,
1002    pub events: Vec<TimelineNodeLayout>,
1003}
1004
1005#[derive(Debug, Clone, Serialize, Deserialize)]
1006#[cfg(feature = "diagram-timeline")]
1007pub struct TimelineSectionLayout {
1008    pub node: TimelineNodeLayout,
1009    pub tasks: Vec<TimelineTaskLayout>,
1010}
1011
1012#[derive(Debug, Clone, Serialize, Deserialize)]
1013#[cfg(feature = "diagram-timeline")]
1014pub struct TimelineDiagramLayout {
1015    pub direction: merman_core::diagrams::timeline::TimelineDirection,
1016    pub bounds: Option<Bounds>,
1017    pub left_margin: f64,
1018    pub base_x: f64,
1019    pub base_y: f64,
1020    /// `svg.node().getBBox().width` computed *before* title/activity line are inserted.
1021    pub pre_title_box_width: f64,
1022    pub sections: Vec<TimelineSectionLayout>,
1023    #[serde(default)]
1024    pub orphan_tasks: Vec<TimelineTaskLayout>,
1025    pub activity_line: TimelineLineLayout,
1026    pub title: Option<String>,
1027    pub title_x: f64,
1028    pub title_y: f64,
1029    #[serde(default = "crate::timeline::default_use_max_width")]
1030    pub use_max_width: bool,
1031}
1032
1033#[derive(Debug, Clone, Serialize, Deserialize)]
1034#[cfg(feature = "diagram-journey")]
1035pub struct JourneyActorLegendLineLayout {
1036    pub text: String,
1037    pub x: f64,
1038    pub y: f64,
1039    pub tspan_x: f64,
1040    pub text_margin: f64,
1041}
1042
1043#[derive(Debug, Clone, Serialize, Deserialize)]
1044#[cfg(feature = "diagram-journey")]
1045pub struct JourneyActorLegendItemLayout {
1046    pub actor: String,
1047    pub pos: i64,
1048    pub color: String,
1049    pub circle_cx: f64,
1050    pub circle_cy: f64,
1051    pub circle_r: f64,
1052    pub label_lines: Vec<JourneyActorLegendLineLayout>,
1053}
1054
1055#[derive(Debug, Clone, Serialize, Deserialize)]
1056#[cfg(feature = "diagram-journey")]
1057pub enum JourneyMouthKind {
1058    Smile,
1059    Sad,
1060    Ambivalent,
1061}
1062
1063#[derive(Debug, Clone, Serialize, Deserialize)]
1064#[cfg(feature = "diagram-journey")]
1065pub struct JourneyTaskActorCircleLayout {
1066    pub actor: String,
1067    pub pos: i64,
1068    pub color: String,
1069    pub cx: f64,
1070    pub cy: f64,
1071    pub r: f64,
1072}
1073
1074#[derive(Debug, Clone, Serialize, Deserialize)]
1075#[cfg(feature = "diagram-journey")]
1076pub struct JourneyTaskLayout {
1077    pub index: i64,
1078    pub section: String,
1079    pub task: String,
1080    pub score: i64,
1081    pub x: f64,
1082    pub y: f64,
1083    pub width: f64,
1084    pub height: f64,
1085    pub fill: String,
1086    pub num: i64,
1087    pub people: Vec<String>,
1088    pub actor_circles: Vec<JourneyTaskActorCircleLayout>,
1089    pub line_id: String,
1090    pub line_x1: f64,
1091    pub line_y1: f64,
1092    pub line_x2: f64,
1093    pub line_y2: f64,
1094    pub face_cx: f64,
1095    pub face_cy: Option<f64>,
1096    pub mouth: JourneyMouthKind,
1097}
1098
1099#[derive(Debug, Clone, Serialize, Deserialize)]
1100#[cfg(feature = "diagram-journey")]
1101pub struct JourneySectionLayout {
1102    pub section: String,
1103    pub num: i64,
1104    pub x: f64,
1105    pub y: f64,
1106    pub width: f64,
1107    pub height: f64,
1108    pub fill: String,
1109    pub task_count: i64,
1110}
1111
1112#[derive(Debug, Clone, Serialize, Deserialize)]
1113#[cfg(feature = "diagram-journey")]
1114pub struct JourneyLineLayout {
1115    pub x1: f64,
1116    pub y1: f64,
1117    pub x2: f64,
1118    pub y2: f64,
1119}
1120
1121#[derive(Debug, Clone, Serialize, Deserialize)]
1122#[cfg(feature = "diagram-journey")]
1123pub struct JourneyDiagramLayout {
1124    pub bounds: Option<Bounds>,
1125    pub left_margin: f64,
1126    pub max_actor_label_width: f64,
1127    pub width: f64,
1128    pub height: f64,
1129    pub svg_height: f64,
1130    #[serde(default = "crate::journey::default_use_max_width")]
1131    pub use_max_width: bool,
1132    pub title: Option<String>,
1133    pub title_x: f64,
1134    pub title_y: f64,
1135    pub actor_legend: Vec<JourneyActorLegendItemLayout>,
1136    pub sections: Vec<JourneySectionLayout>,
1137    pub tasks: Vec<JourneyTaskLayout>,
1138    pub activity_line: JourneyLineLayout,
1139}
1140
1141#[derive(Debug, Clone, Serialize, Deserialize)]
1142#[cfg(feature = "diagram-kanban")]
1143pub struct KanbanSectionLayout {
1144    pub id: String,
1145    pub label: String,
1146    pub index: i64,
1147    pub center_x: f64,
1148    pub center_y: f64,
1149    pub width: f64,
1150    pub rect_y: f64,
1151    pub rect_height: f64,
1152    pub rx: f64,
1153    pub ry: f64,
1154    pub label_width: f64,
1155    #[serde(skip)]
1156    pub label_height: f64,
1157}
1158
1159#[derive(Debug, Clone, Serialize, Deserialize)]
1160#[cfg(feature = "diagram-kanban")]
1161pub struct KanbanItemLayout {
1162    pub id: String,
1163    pub label: String,
1164    pub parent_id: String,
1165    pub center_x: f64,
1166    pub center_y: f64,
1167    pub width: f64,
1168    pub height: f64,
1169    pub rx: f64,
1170    pub ry: f64,
1171    #[serde(default)]
1172    pub ticket: Option<String>,
1173    #[serde(default)]
1174    pub assigned: Option<String>,
1175    #[serde(default)]
1176    pub priority: Option<String>,
1177    #[serde(default)]
1178    pub icon: Option<String>,
1179}
1180
1181#[derive(Debug, Clone, Serialize, Deserialize)]
1182#[cfg(feature = "diagram-kanban")]
1183pub struct KanbanDiagramLayout {
1184    pub bounds: Option<Bounds>,
1185    pub section_width: f64,
1186    pub padding: f64,
1187    pub max_label_height: f64,
1188    pub viewbox_padding: f64,
1189    #[serde(default = "crate::kanban::default_use_max_width")]
1190    pub use_max_width: bool,
1191    pub sections: Vec<KanbanSectionLayout>,
1192    pub items: Vec<KanbanItemLayout>,
1193}
1194
1195#[derive(Debug, Clone, Serialize, Deserialize)]
1196#[cfg(feature = "diagram-git-graph")]
1197pub struct GitGraphBranchLayout {
1198    pub name: String,
1199    pub index: i64,
1200    pub pos: f64,
1201    pub bbox_width: f64,
1202    pub bbox_height: f64,
1203}
1204
1205#[derive(Debug, Clone, Serialize, Deserialize)]
1206#[cfg(feature = "diagram-git-graph")]
1207pub struct GitGraphCommitLayout {
1208    pub id: String,
1209    pub message: String,
1210    pub seq: i64,
1211    pub commit_type: i64,
1212    #[serde(default)]
1213    pub custom_type: Option<i64>,
1214    #[serde(default)]
1215    pub custom_id: Option<bool>,
1216    #[serde(default)]
1217    pub tags: Vec<String>,
1218    #[serde(default)]
1219    pub parents: Vec<String>,
1220    pub branch: String,
1221    pub pos: f64,
1222    pub pos_with_offset: f64,
1223    pub x: f64,
1224    pub y: f64,
1225}
1226
1227#[derive(Debug, Clone, Serialize, Deserialize)]
1228#[cfg(feature = "diagram-git-graph")]
1229pub struct GitGraphArrowLayout {
1230    pub from: String,
1231    pub to: String,
1232    pub class_index: i64,
1233    pub d: String,
1234}
1235
1236#[derive(Debug, Clone, Serialize, Deserialize)]
1237#[cfg(feature = "diagram-git-graph")]
1238pub struct GitGraphDiagramLayout {
1239    pub bounds: Option<Bounds>,
1240    pub direction: String,
1241    pub rotate_commit_label: bool,
1242    pub show_branches: bool,
1243    pub show_commit_label: bool,
1244    pub parallel_commits: bool,
1245    pub diagram_padding: f64,
1246    pub max_pos: f64,
1247    pub branches: Vec<GitGraphBranchLayout>,
1248    pub commits: Vec<GitGraphCommitLayout>,
1249    pub arrows: Vec<GitGraphArrowLayout>,
1250}
1251
1252#[derive(Debug, Clone, Serialize, Deserialize)]
1253#[cfg(feature = "diagram-tree-view")]
1254pub struct TreeViewNodeLayout {
1255    pub id: i64,
1256    pub level: i64,
1257    pub name: String,
1258    #[serde(rename = "nodeType")]
1259    pub node_type: String,
1260    #[serde(default, rename = "cssClass", skip_serializing_if = "Option::is_none")]
1261    pub css_class: Option<String>,
1262    #[serde(default, skip_serializing_if = "Option::is_none")]
1263    pub icon: Option<String>,
1264    #[serde(default, skip_serializing_if = "Option::is_none")]
1265    pub resolved_icon: Option<String>,
1266    #[serde(default, skip_serializing_if = "Option::is_none")]
1267    pub description: Option<String>,
1268    pub depth: usize,
1269    pub x: f64,
1270    pub y: f64,
1271    pub width: f64,
1272    pub height: f64,
1273    pub label_x: f64,
1274    pub label_y: f64,
1275    pub label_width: f64,
1276    pub label_height: f64,
1277    #[serde(default, skip_serializing_if = "Option::is_none")]
1278    pub description_x: Option<f64>,
1279    #[serde(default, skip_serializing_if = "Option::is_none")]
1280    pub description_width: Option<f64>,
1281}
1282
1283#[derive(Debug, Clone, Serialize, Deserialize)]
1284#[cfg(feature = "diagram-tree-view")]
1285pub struct TreeViewLineLayout {
1286    pub x1: f64,
1287    pub y1: f64,
1288    pub x2: f64,
1289    pub y2: f64,
1290    pub stroke_width: f64,
1291    pub kind: String,
1292}
1293
1294#[derive(Debug, Clone, Serialize, Deserialize)]
1295#[cfg(feature = "diagram-tree-view")]
1296pub struct TreeViewDiagramLayout {
1297    pub bounds: Option<Bounds>,
1298    pub total_width: f64,
1299    pub total_height: f64,
1300    pub row_indent: f64,
1301    pub padding_x: f64,
1302    pub padding_y: f64,
1303    pub line_thickness: f64,
1304    pub use_max_width: bool,
1305    pub label_font_size: f64,
1306    pub nodes: Vec<TreeViewNodeLayout>,
1307    pub lines: Vec<TreeViewLineLayout>,
1308}
1309
1310#[derive(Debug, Clone, Serialize, Deserialize)]
1311#[cfg(feature = "diagram-ishikawa")]
1312pub struct IshikawaLineLayout {
1313    pub x1: f64,
1314    pub y1: f64,
1315    pub x2: f64,
1316    pub y2: f64,
1317    pub class_name: String,
1318    pub marker_start: bool,
1319}
1320
1321#[derive(Debug, Clone, Serialize, Deserialize)]
1322#[cfg(feature = "diagram-ishikawa")]
1323pub struct IshikawaTextLayout {
1324    pub text: String,
1325    pub lines: Vec<String>,
1326    pub class_name: String,
1327    pub x: f64,
1328    pub y: f64,
1329    pub anchor: String,
1330    pub line_height: f64,
1331    pub font_size: f64,
1332    pub bbox: Bounds,
1333}
1334
1335#[derive(Debug, Clone, Serialize, Deserialize)]
1336#[cfg(feature = "diagram-ishikawa")]
1337pub struct IshikawaLabelBoxLayout {
1338    pub x: f64,
1339    pub y: f64,
1340    pub width: f64,
1341    pub height: f64,
1342}
1343
1344#[derive(Debug, Clone, Serialize, Deserialize)]
1345#[cfg(feature = "diagram-ishikawa")]
1346pub struct IshikawaCauseLabelGroupLayout {
1347    pub label_box: IshikawaLabelBoxLayout,
1348    pub label: IshikawaTextLayout,
1349}
1350
1351#[derive(Debug, Clone, Serialize, Deserialize)]
1352#[cfg(feature = "diagram-ishikawa")]
1353pub struct IshikawaSubGroupLayout {
1354    pub line: IshikawaLineLayout,
1355    pub label: IshikawaTextLayout,
1356}
1357
1358#[derive(Debug, Clone, Serialize, Deserialize)]
1359#[cfg(feature = "diagram-ishikawa")]
1360pub struct IshikawaBranchLayout {
1361    pub line: IshikawaLineLayout,
1362    pub label_group: IshikawaCauseLabelGroupLayout,
1363    pub sub_groups: Vec<IshikawaSubGroupLayout>,
1364}
1365
1366#[derive(Debug, Clone, Serialize, Deserialize)]
1367#[cfg(feature = "diagram-ishikawa")]
1368pub struct IshikawaPairLayout {
1369    pub upper: IshikawaBranchLayout,
1370    pub lower: Option<IshikawaBranchLayout>,
1371}
1372
1373#[derive(Debug, Clone, Serialize, Deserialize)]
1374#[cfg(feature = "diagram-ishikawa")]
1375pub struct IshikawaHeadLayout {
1376    pub x: f64,
1377    pub y: f64,
1378    pub width: f64,
1379    pub height: f64,
1380    pub path_d: String,
1381    pub label: IshikawaTextLayout,
1382}
1383
1384#[derive(Debug, Clone, Serialize, Deserialize)]
1385#[cfg(feature = "diagram-ishikawa")]
1386pub struct IshikawaDiagramLayout {
1387    pub bounds: Option<Bounds>,
1388    pub total_width: f64,
1389    pub total_height: f64,
1390    pub viewbox_x: f64,
1391    pub viewbox_y: f64,
1392    pub padding: f64,
1393    pub use_max_width: bool,
1394    pub font_size: f64,
1395    pub head: Option<IshikawaHeadLayout>,
1396    pub spine: Option<IshikawaLineLayout>,
1397    #[serde(default)]
1398    pub pairs: Vec<IshikawaPairLayout>,
1399}
1400
1401#[derive(Debug, Clone, Serialize, Deserialize)]
1402#[cfg(feature = "diagram-event-modeling")]
1403pub struct EventModelingSwimlaneLayout {
1404    pub index: i64,
1405    pub label: String,
1406    pub namespace: Option<String>,
1407    pub x: f64,
1408    pub y: f64,
1409    pub width: f64,
1410    pub height: f64,
1411}
1412
1413#[derive(Debug, Clone, Serialize, Deserialize)]
1414#[cfg(feature = "diagram-event-modeling")]
1415pub struct EventModelingBoxLayout {
1416    pub index: usize,
1417    pub frame_name: String,
1418    pub frame_kind: String,
1419    pub model_entity_type: String,
1420    pub entity_identifier: String,
1421    pub text: String,
1422    pub x: f64,
1423    pub y: f64,
1424    pub width: f64,
1425    pub height: f64,
1426    pub fill: String,
1427    pub stroke: String,
1428    pub swimlane_index: i64,
1429}
1430
1431#[derive(Debug, Clone, Serialize, Deserialize)]
1432#[cfg(feature = "diagram-event-modeling")]
1433pub struct EventModelingRelationLayout {
1434    pub source_frame: String,
1435    pub target_frame: String,
1436    pub x1: f64,
1437    pub y1: f64,
1438    pub x2: f64,
1439    pub y2: f64,
1440    pub stroke: String,
1441}
1442
1443#[derive(Debug, Clone, Serialize, Deserialize)]
1444#[cfg(feature = "diagram-event-modeling")]
1445pub struct EventModelingDiagramLayout {
1446    pub bounds: Option<Bounds>,
1447    pub total_width: f64,
1448    pub total_height: f64,
1449    pub viewbox_x: f64,
1450    pub viewbox_y: f64,
1451    pub padding: f64,
1452    pub use_max_width: bool,
1453    pub swimlanes: Vec<EventModelingSwimlaneLayout>,
1454    pub boxes: Vec<EventModelingBoxLayout>,
1455    pub relations: Vec<EventModelingRelationLayout>,
1456}
1457
1458#[derive(Debug, Clone, Serialize, Deserialize)]
1459#[cfg(feature = "diagram-cynefin")]
1460pub struct CynefinDomainLayout {
1461    pub name: String,
1462    pub cx: f64,
1463    pub cy: f64,
1464    pub x: f64,
1465    pub y: f64,
1466    pub width: f64,
1467    pub height: f64,
1468}
1469
1470#[derive(Debug, Clone, Serialize, Deserialize)]
1471#[cfg(feature = "diagram-cynefin")]
1472pub struct CynefinItemLayout {
1473    pub domain: String,
1474    pub label: String,
1475    pub x: f64,
1476    pub y: f64,
1477    pub width: f64,
1478    pub height: f64,
1479    pub text_x: f64,
1480    pub text_y: f64,
1481    pub overflow: bool,
1482}
1483
1484#[derive(Debug, Clone, Serialize, Deserialize)]
1485#[cfg(feature = "diagram-cynefin")]
1486pub struct CynefinTransitionLayout {
1487    pub from: String,
1488    pub to: String,
1489    pub label: Option<String>,
1490    pub x1: f64,
1491    pub y1: f64,
1492    pub x2: f64,
1493    pub y2: f64,
1494    pub cpx: f64,
1495    pub cpy: f64,
1496}
1497
1498#[derive(Debug, Clone, Serialize, Deserialize)]
1499#[cfg(feature = "diagram-cynefin")]
1500pub struct CynefinDiagramLayout {
1501    pub bounds: Option<Bounds>,
1502    pub width: f64,
1503    pub height: f64,
1504    pub padding: f64,
1505    pub total_width: f64,
1506    pub total_height: f64,
1507    pub use_max_width: bool,
1508    pub show_domain_descriptions: bool,
1509    pub boundary_amplitude: f64,
1510    #[serde(default, skip_serializing_if = "Option::is_none")]
1511    pub seed: Option<f64>,
1512    pub domain_layouts: Vec<CynefinDomainLayout>,
1513    pub items: Vec<CynefinItemLayout>,
1514    pub transitions: Vec<CynefinTransitionLayout>,
1515}
1516
1517#[derive(Debug, Clone, Serialize, Deserialize)]
1518#[cfg(feature = "diagram-railroad")]
1519pub struct RailroadDiagramLayout {
1520    pub bounds: Option<Bounds>,
1521    pub diagram_type: String,
1522    pub width: f64,
1523    pub height: f64,
1524    pub use_max_width: bool,
1525    pub rules: Vec<RailroadRuleLayout>,
1526}
1527
1528#[derive(Debug, Clone, Serialize, Deserialize)]
1529#[cfg(feature = "diagram-railroad")]
1530pub struct RailroadRuleLayout {
1531    pub name: String,
1532    pub x: f64,
1533    pub y: f64,
1534    pub width: f64,
1535    pub height: f64,
1536    pub baseline_y: f64,
1537    pub name_width: f64,
1538    pub definition_x: f64,
1539    pub start_marker_x: f64,
1540    pub end_marker_x: f64,
1541    pub marker_radius: f64,
1542    pub elements: Vec<RailroadElementLayout>,
1543    pub paths: Vec<RailroadPathLayout>,
1544}
1545
1546#[derive(Debug, Clone, Serialize, Deserialize)]
1547#[cfg(feature = "diagram-railroad")]
1548pub struct RailroadElementLayout {
1549    pub kind: String,
1550    pub label: String,
1551    pub x: f64,
1552    pub y: f64,
1553    pub width: f64,
1554    pub height: f64,
1555    pub text_x: f64,
1556    pub text_y: f64,
1557}
1558
1559#[derive(Debug, Clone, Serialize, Deserialize)]
1560#[cfg(feature = "diagram-railroad")]
1561pub struct RailroadPathLayout {
1562    #[serde(default)]
1563    pub x: f64,
1564    #[serde(default)]
1565    pub y: f64,
1566    pub d: String,
1567}
1568
1569#[derive(Debug, Clone, Serialize, Deserialize)]
1570#[cfg(feature = "diagram-gantt")]
1571pub struct GanttAxisTickLayout {
1572    pub time_ms: i64,
1573    pub x: f64,
1574    pub label: String,
1575}
1576
1577#[derive(Debug, Clone, Serialize, Deserialize)]
1578#[cfg(feature = "diagram-gantt")]
1579pub struct GanttExcludeRangeLayout {
1580    pub id: String,
1581    pub start_ms: i64,
1582    pub end_ms: i64,
1583    pub x: f64,
1584    pub y: f64,
1585    pub width: f64,
1586    pub height: f64,
1587}
1588
1589#[derive(Debug, Clone, Serialize, Deserialize)]
1590#[cfg(feature = "diagram-gantt")]
1591pub struct GanttSectionTitleLayout {
1592    pub section: String,
1593    pub index: i64,
1594    pub x: f64,
1595    pub y: f64,
1596    pub dy_em: f64,
1597    pub lines: Vec<String>,
1598    pub class: String,
1599}
1600
1601#[derive(Debug, Clone, Serialize, Deserialize)]
1602#[cfg(feature = "diagram-gantt")]
1603pub struct GanttRowLayout {
1604    pub index: i64,
1605    pub x: f64,
1606    pub y: f64,
1607    pub width: f64,
1608    pub height: f64,
1609    pub class: String,
1610}
1611
1612#[derive(Debug, Clone, Serialize, Deserialize)]
1613#[cfg(feature = "diagram-gantt")]
1614pub struct GanttTaskLabelLayout {
1615    pub id: String,
1616    pub text: String,
1617    pub font_size: f64,
1618    pub width: f64,
1619    pub x: f64,
1620    pub y: f64,
1621    pub class: String,
1622}
1623
1624#[derive(Debug, Clone, Serialize, Deserialize)]
1625#[cfg(feature = "diagram-gantt")]
1626pub struct GanttTaskBarLayout {
1627    pub id: String,
1628    pub x: f64,
1629    pub y: f64,
1630    pub width: f64,
1631    pub height: f64,
1632    pub rx: f64,
1633    pub ry: f64,
1634    pub class: String,
1635}
1636
1637#[derive(Debug, Clone, Serialize, Deserialize)]
1638#[cfg(feature = "diagram-gantt")]
1639pub struct GanttTaskLayout {
1640    pub id: String,
1641    pub task: String,
1642    pub section: String,
1643    pub task_type: String,
1644    pub order: i64,
1645    pub start_ms: i64,
1646    pub end_ms: i64,
1647    #[serde(default)]
1648    pub render_end_ms: Option<i64>,
1649    pub milestone: bool,
1650    pub vert: bool,
1651    pub bar: GanttTaskBarLayout,
1652    pub label: GanttTaskLabelLayout,
1653}
1654
1655#[derive(Debug, Clone, Serialize, Deserialize)]
1656#[cfg(feature = "diagram-gantt")]
1657pub struct GanttDiagramLayout {
1658    pub bounds: Option<Bounds>,
1659    pub width: f64,
1660    pub height: f64,
1661    pub left_padding: f64,
1662    pub right_padding: f64,
1663    pub top_padding: f64,
1664    pub grid_line_start_padding: f64,
1665    pub bar_height: f64,
1666    pub bar_gap: f64,
1667    pub title_top_margin: f64,
1668    pub font_size: f64,
1669    pub section_font_size: f64,
1670    pub number_section_styles: i64,
1671    pub display_mode: String,
1672    pub date_format: String,
1673    pub axis_format: String,
1674    #[serde(default)]
1675    pub tick_interval: Option<String>,
1676    pub top_axis: bool,
1677    pub today_marker: String,
1678    pub categories: Vec<String>,
1679    pub rows: Vec<GanttRowLayout>,
1680    pub section_titles: Vec<GanttSectionTitleLayout>,
1681    pub tasks: Vec<GanttTaskLayout>,
1682    pub excludes: Vec<GanttExcludeRangeLayout>,
1683    #[serde(default)]
1684    pub has_excludes_layer: bool,
1685    pub bottom_ticks: Vec<GanttAxisTickLayout>,
1686    #[serde(default)]
1687    pub top_ticks: Vec<GanttAxisTickLayout>,
1688    pub title: Option<String>,
1689    pub title_x: f64,
1690    pub title_y: f64,
1691}
1692
1693#[derive(Debug, Clone)]
1694#[cfg(feature = "diagram-c4")]
1695pub(crate) struct C4TextRowLayout {
1696    /// Source words emitted into one Mermaid `createText` outer tspan.
1697    ///
1698    /// Keeping the source words (rather than only a joined string) preserves the same
1699    /// whitespace/token boundaries for layout and SVG emission.
1700    pub(crate) words: Vec<String>,
1701}
1702
1703#[derive(Debug, Clone)]
1704#[cfg(feature = "diagram-c4")]
1705pub(crate) struct C4TextRenderPlan {
1706    /// Wrapped source-word rows consumed by the C4 SVG emitter.
1707    pub(crate) rows: Vec<C4TextRowLayout>,
1708    /// The section bbox x offset relative to the centered outer tspan anchor.
1709    pub(crate) bbox_x: f64,
1710    /// The section bbox y offset produced by Mermaid's createText DOM shape.
1711    pub(crate) bbox_y: f64,
1712}
1713
1714#[derive(Debug, Clone, Serialize, Deserialize)]
1715#[cfg(feature = "diagram-c4")]
1716pub struct C4TextBlockLayout {
1717    pub text: String,
1718    pub y: f64,
1719    pub width: f64,
1720    pub height: f64,
1721    pub line_count: usize,
1722    /// Unified C4 labels carry the exact wrapped rows used during measurement. Legacy C4
1723    /// boundary and relationship labels leave this unset and continue using their historical
1724    /// text path.
1725    /// This is an in-memory render artifact; the public layout projection intentionally keeps
1726    /// the stable historical C4 layout schema and does not expose tokenization details.
1727    #[serde(skip)]
1728    pub(crate) render_plan: Option<C4TextRenderPlan>,
1729}
1730
1731#[derive(Debug, Clone, Serialize, Deserialize)]
1732#[cfg(feature = "diagram-c4")]
1733pub struct C4ImageLayout {
1734    pub width: f64,
1735    pub height: f64,
1736    pub y: f64,
1737}
1738
1739#[derive(Debug, Clone, Serialize, Deserialize)]
1740#[cfg(feature = "diagram-c4")]
1741pub struct C4ShapeLayout {
1742    pub alias: String,
1743    pub parent_boundary: String,
1744    pub type_c4_shape: String,
1745    pub x: f64,
1746    pub y: f64,
1747    pub width: f64,
1748    pub height: f64,
1749    pub margin: f64,
1750    pub image: C4ImageLayout,
1751    pub type_block: C4TextBlockLayout,
1752    pub label: C4TextBlockLayout,
1753    #[serde(default)]
1754    pub ty: Option<C4TextBlockLayout>,
1755    #[serde(default)]
1756    pub techn: Option<C4TextBlockLayout>,
1757    #[serde(default)]
1758    pub descr: Option<C4TextBlockLayout>,
1759}
1760
1761#[derive(Debug, Clone, Serialize, Deserialize)]
1762#[cfg(feature = "diagram-c4")]
1763pub struct C4BoundaryLayout {
1764    pub alias: String,
1765    pub parent_boundary: String,
1766    pub x: f64,
1767    pub y: f64,
1768    pub width: f64,
1769    pub height: f64,
1770    pub image: C4ImageLayout,
1771    pub label: C4TextBlockLayout,
1772    #[serde(default)]
1773    pub ty: Option<C4TextBlockLayout>,
1774    #[serde(default)]
1775    pub descr: Option<C4TextBlockLayout>,
1776}
1777
1778#[derive(Debug, Clone, Serialize, Deserialize)]
1779#[cfg(feature = "diagram-c4")]
1780pub struct C4RelLayout {
1781    pub from: String,
1782    pub to: String,
1783    pub rel_type: String,
1784    pub start_point: LayoutPoint,
1785    pub end_point: LayoutPoint,
1786    #[serde(default)]
1787    pub offset_x: Option<i64>,
1788    #[serde(default)]
1789    pub offset_y: Option<i64>,
1790    pub label: C4TextBlockLayout,
1791    #[serde(default)]
1792    pub techn: Option<C4TextBlockLayout>,
1793    #[serde(default)]
1794    pub descr: Option<C4TextBlockLayout>,
1795}
1796
1797#[derive(Debug, Clone, Serialize, Deserialize)]
1798#[non_exhaustive]
1799#[cfg(feature = "diagram-c4")]
1800pub struct C4DiagramLayout {
1801    pub bounds: Option<Bounds>,
1802    pub width: f64,
1803    pub height: f64,
1804    #[serde(default = "crate::c4::default_use_max_width")]
1805    pub use_max_width: bool,
1806    pub container_width: f64,
1807    pub container_height: f64,
1808    #[serde(default, skip_serializing_if = "Option::is_none")]
1809    pub screen_available_width: Option<f64>,
1810    pub c4_type: String,
1811    pub title: Option<String>,
1812    pub boundaries: Vec<C4BoundaryLayout>,
1813    pub shapes: Vec<C4ShapeLayout>,
1814    pub rels: Vec<C4RelLayout>,
1815}
1816
1817#[derive(Debug, Clone, Serialize, Deserialize)]
1818pub struct ErrorDiagramLayout {
1819    pub viewbox_width: f64,
1820    pub viewbox_height: f64,
1821    pub max_width_px: f64,
1822}