Skip to main content

merman_render/svg/
parity.rs

1use super::icon_registry::IconRegistry;
2use super::pipeline::{ScopedCssPostprocessor, SvgPipeline, SvgPostprocessMetadata};
3use crate::model::{
4    ArchitectureDiagramLayout, BlockDiagramLayout, Bounds, ClassDiagramV2Layout, ErDiagramLayout,
5    ErrorDiagramLayout, EventModelingDiagramLayout, FlowchartV2Layout, InfoDiagramLayout,
6    IshikawaDiagramLayout, LayoutCluster, LayoutNode, MindmapDiagramLayout, PacketDiagramLayout,
7    PieDiagramLayout, QuadrantChartDiagramLayout, RadarDiagramLayout, RequirementDiagramLayout,
8    SankeyDiagramLayout, SequenceDiagramLayout, StateDiagramV2Layout, TimelineDiagramLayout,
9    TreeViewDiagramLayout, VennDiagramLayout, XyChartDiagramLayout,
10};
11use crate::text::{TextMeasurer, TextStyle, WrapMode};
12use crate::{Error, Result};
13use base64::Engine as _;
14use indexmap::IndexMap;
15use std::fmt::Write as _;
16
17mod architecture;
18mod block;
19mod c4;
20mod class;
21mod css;
22mod curve;
23mod emitted_bounds;
24mod er;
25mod error;
26mod eventmodeling;
27mod flowchart;
28mod gantt;
29mod gitgraph;
30mod info;
31mod ishikawa;
32mod journey;
33mod kanban;
34mod layout_debug;
35mod mindmap;
36mod packet;
37mod path_bounds;
38mod pie;
39mod quadrantchart;
40mod radar;
41mod requirement;
42mod root_svg;
43mod roughjs_common;
44mod sankey;
45mod sequence;
46mod state;
47mod style;
48pub(crate) mod theme;
49mod timeline;
50mod timing;
51mod tree_view;
52mod treemap;
53mod util;
54mod venn;
55mod xychart;
56use crate::math::MathRenderer;
57use css::{
58    er_css, gantt_css, info_css_parts_with_config, info_css_parts_with_theme_font_size_only,
59    info_css_with_config, pie_css, push_xychart_css, requirement_css, sankey_css, treemap_css,
60};
61use path_bounds::svg_path_bounds_from_d;
62pub(crate) fn mindmap_cloud_rendered_bbox_size_px(w: f64, h: f64) -> Option<(f64, f64)> {
63    mindmap::mindmap_cloud_rendered_bbox_size_px(w, h)
64}
65pub use emitted_bounds::{
66    SvgEmittedBoundsContributor, SvgEmittedBoundsDebug, debug_svg_emitted_bounds,
67};
68use emitted_bounds::{svg_emitted_bounds_from_svg, svg_emitted_bounds_from_svg_inner};
69use state::{roughjs_ops_to_svg_path_d, roughjs_parse_hex_color_to_srgba, roughjs_paths_for_rect};
70use style::{is_rect_style_key, is_text_style_key, parse_style_decl};
71use theme::PresentationTheme;
72use util::{
73    SvgTheme, apply_root_viewport_override, config_bool, config_diagram_look, config_f64,
74    config_f64_css_px, config_string, css_rgba_fade, decode_mermaid_entities_for_render_text,
75    escape_attr, escape_attr_display, escape_attr_into, escape_xml, escape_xml_display,
76    escape_xml_into, fmt, fmt_debug_3dp, fmt_display, fmt_into, fmt_max_width_px, fmt_path,
77    fmt_path_into, fmt_points, fmt_string, json_stringify_points, json_stringify_points_into,
78    normalize_css_font_family, push_points_attr, scoped_svg_id, scoped_svg_url, theme_color,
79};
80
81const MERMAID_SEQUENCE_BASE_DEFS_11_12_2: &str = include_str!(concat!(
82    env!("CARGO_MANIFEST_DIR"),
83    "/assets/sequence_base_defs_11_12_2.svgfrag"
84));
85
86#[derive(Debug, Clone)]
87pub struct SvgRenderOptions {
88    /// Adds extra space around the computed viewBox.
89    pub viewbox_padding: f64,
90    /// Optional diagram id used for Mermaid-like marker ids.
91    pub diagram_id: Option<String>,
92    /// Optional override for the root SVG `aria-roledescription` attribute.
93    ///
94    /// This is primarily used to reproduce Mermaid's per-header accessibility metadata quirks
95    /// (e.g. `classDiagram-v2` differs from `classDiagram` at Mermaid 11.12.2).
96    pub aria_roledescription: Option<String>,
97    /// When true, include edge polylines.
98    pub include_edges: bool,
99    /// When true, include node bounding boxes and ids.
100    pub include_nodes: bool,
101    /// When true, include cluster bounding boxes and titles.
102    pub include_clusters: bool,
103    /// When true, draw markers that visualize Mermaid cluster positioning metadata.
104    pub include_cluster_debug_markers: bool,
105    /// When true, label edge routes with edge ids.
106    pub include_edge_id_labels: bool,
107    /// Optional override for "current time" used by diagrams that render time-dependent markers
108    /// (e.g. Gantt `today` line). This exists to make parity/golden comparisons reproducible.
109    pub now_ms_override: Option<i64>,
110    /// Optional math renderer for `$$...$$` style labels.
111    pub math_renderer: Option<std::sync::Arc<dyn MathRenderer + Send + Sync>>,
112    /// Optional Iconify-compatible registry used by icon-capable renderers.
113    pub icon_registry: Option<std::sync::Arc<IconRegistry>>,
114    /// When false, renderers that support root viewport override lookup emit computed bounds.
115    pub apply_root_overrides: bool,
116}
117
118impl Default for SvgRenderOptions {
119    fn default() -> Self {
120        Self {
121            viewbox_padding: 8.0,
122            diagram_id: None,
123            aria_roledescription: None,
124            include_edges: true,
125            include_nodes: true,
126            include_clusters: true,
127            include_cluster_debug_markers: false,
128            include_edge_id_labels: false,
129            now_ms_override: None,
130            math_renderer: None,
131            icon_registry: None,
132            apply_root_overrides: true,
133        }
134    }
135}
136
137pub fn render_layouted_svg(
138    diagram: &crate::model::LayoutedDiagram,
139    measurer: &dyn TextMeasurer,
140    options: &SvgRenderOptions,
141) -> Result<String> {
142    render_layout_svg_parts(
143        &diagram.layout,
144        &diagram.semantic,
145        &diagram.meta.effective_config,
146        diagram.meta.title.as_deref(),
147        measurer,
148        options,
149    )
150}
151
152pub fn render_layout_svg_parts(
153    layout: &crate::model::LayoutDiagram,
154    semantic: &serde_json::Value,
155    effective_config: &serde_json::Value,
156    title: Option<&str>,
157    measurer: &dyn TextMeasurer,
158    options: &SvgRenderOptions,
159) -> Result<String> {
160    let svg =
161        render_layout_svg_parts_raw(layout, semantic, effective_config, title, measurer, options)?;
162    apply_theme_css(svg, effective_config)
163}
164
165fn render_layout_svg_parts_raw(
166    layout: &crate::model::LayoutDiagram,
167    semantic: &serde_json::Value,
168    effective_config: &serde_json::Value,
169    title: Option<&str>,
170    measurer: &dyn TextMeasurer,
171    options: &SvgRenderOptions,
172) -> Result<String> {
173    use crate::model::LayoutDiagram;
174
175    match layout {
176        LayoutDiagram::ErrorDiagram(layout) => {
177            render_error_diagram_svg(layout, semantic, effective_config, options)
178        }
179        LayoutDiagram::BlockDiagram(layout) => {
180            render_block_diagram_svg(layout, semantic, effective_config, options)
181        }
182        LayoutDiagram::RequirementDiagram(layout) => {
183            render_requirement_diagram_svg(layout, semantic, effective_config, title, options)
184        }
185        LayoutDiagram::ArchitectureDiagram(layout) => {
186            render_architecture_diagram_svg(layout, semantic, effective_config, options)
187        }
188        LayoutDiagram::MindmapDiagram(layout) => {
189            render_mindmap_diagram_svg(layout, semantic, effective_config, options)
190        }
191        LayoutDiagram::SankeyDiagram(layout) => {
192            render_sankey_diagram_svg(layout, semantic, effective_config, options)
193        }
194        LayoutDiagram::RadarDiagram(layout) => {
195            render_radar_diagram_svg(layout, semantic, effective_config, options)
196        }
197        LayoutDiagram::TreemapDiagram(layout) => {
198            render_treemap_diagram_svg(layout, semantic, effective_config, options)
199        }
200        LayoutDiagram::VennDiagram(layout) => {
201            render_venn_diagram_svg(layout, semantic, effective_config, title, options)
202        }
203        LayoutDiagram::XyChartDiagram(layout) => {
204            render_xychart_diagram_svg(layout, semantic, effective_config, options)
205        }
206        LayoutDiagram::QuadrantChartDiagram(layout) => {
207            render_quadrantchart_diagram_svg(layout, semantic, effective_config, options)
208        }
209        LayoutDiagram::FlowchartV2(layout) => {
210            render_flowchart_v2_svg(layout, semantic, effective_config, title, measurer, options)
211        }
212        LayoutDiagram::StateDiagramV2(layout) => render_state_diagram_v2_svg(
213            layout,
214            semantic,
215            effective_config,
216            title,
217            measurer,
218            options,
219        ),
220        LayoutDiagram::ClassDiagramV2(layout) => render_class_diagram_v2_svg(
221            layout,
222            semantic,
223            effective_config,
224            title,
225            measurer,
226            options,
227        ),
228        LayoutDiagram::ErDiagram(layout) => {
229            render_er_diagram_svg(layout, semantic, effective_config, title, measurer, options)
230        }
231        LayoutDiagram::SequenceDiagram(layout) => render_sequence_diagram_svg(
232            layout,
233            semantic,
234            effective_config,
235            title,
236            measurer,
237            options,
238        ),
239        LayoutDiagram::InfoDiagram(layout) => {
240            render_info_diagram_svg(layout, semantic, effective_config, options)
241        }
242        LayoutDiagram::PacketDiagram(layout) => {
243            render_packet_diagram_svg(layout, semantic, effective_config, title, options)
244        }
245        LayoutDiagram::TimelineDiagram(layout) => render_timeline_diagram_svg(
246            layout,
247            semantic,
248            effective_config,
249            title,
250            measurer,
251            options,
252        ),
253        LayoutDiagram::PieDiagram(layout) => {
254            render_pie_diagram_svg(layout, semantic, effective_config, options)
255        }
256        LayoutDiagram::JourneyDiagram(layout) => {
257            render_journey_diagram_svg(layout, semantic, effective_config, title, measurer, options)
258        }
259        LayoutDiagram::KanbanDiagram(layout) => {
260            render_kanban_diagram_svg(layout, semantic, effective_config, options)
261        }
262        LayoutDiagram::GitGraphDiagram(layout) => render_gitgraph_diagram_svg(
263            layout,
264            semantic,
265            effective_config,
266            title,
267            measurer,
268            options,
269        ),
270        LayoutDiagram::GanttDiagram(layout) => {
271            render_gantt_diagram_svg(layout, semantic, effective_config, options)
272        }
273        LayoutDiagram::TreeViewDiagram(layout) => {
274            render_tree_view_diagram_svg(layout, semantic, effective_config, options)
275        }
276        LayoutDiagram::IshikawaDiagram(layout) => {
277            render_ishikawa_diagram_svg(layout, semantic, effective_config, options)
278        }
279        LayoutDiagram::EventModelingDiagram(layout) => {
280            render_eventmodeling_diagram_svg(layout, semantic, effective_config, options)
281        }
282        LayoutDiagram::C4Diagram(layout) => {
283            render_c4_diagram_svg(layout, semantic, effective_config, title, measurer, options)
284        }
285    }
286}
287
288pub fn render_layout_svg_parts_with_config(
289    layout: &crate::model::LayoutDiagram,
290    semantic: &serde_json::Value,
291    effective_config: &merman_core::MermaidConfig,
292    title: Option<&str>,
293    measurer: &dyn TextMeasurer,
294    options: &SvgRenderOptions,
295) -> Result<String> {
296    let svg = render_layout_svg_parts_with_config_raw(
297        layout,
298        semantic,
299        effective_config,
300        title,
301        measurer,
302        options,
303    )?;
304    apply_theme_css(svg, effective_config.as_value())
305}
306
307fn render_layout_svg_parts_with_config_raw(
308    layout: &crate::model::LayoutDiagram,
309    semantic: &serde_json::Value,
310    effective_config: &merman_core::MermaidConfig,
311    title: Option<&str>,
312    measurer: &dyn TextMeasurer,
313    options: &SvgRenderOptions,
314) -> Result<String> {
315    use crate::model::LayoutDiagram;
316
317    let effective_config_value = effective_config.as_value();
318
319    match layout {
320        LayoutDiagram::ErrorDiagram(layout) => {
321            render_error_diagram_svg(layout, semantic, effective_config_value, options)
322        }
323        LayoutDiagram::BlockDiagram(layout) => {
324            render_block_diagram_svg(layout, semantic, effective_config_value, options)
325        }
326        LayoutDiagram::RequirementDiagram(layout) => {
327            render_requirement_diagram_svg(layout, semantic, effective_config_value, title, options)
328        }
329        LayoutDiagram::ArchitectureDiagram(layout) => {
330            architecture::render_architecture_diagram_svg_with_config(
331                layout,
332                semantic,
333                effective_config,
334                options,
335            )
336        }
337        LayoutDiagram::MindmapDiagram(layout) => {
338            render_mindmap_diagram_svg_with_config(layout, semantic, effective_config, options)
339        }
340        LayoutDiagram::SankeyDiagram(layout) => {
341            render_sankey_diagram_svg(layout, semantic, effective_config_value, options)
342        }
343        LayoutDiagram::RadarDiagram(layout) => {
344            render_radar_diagram_svg(layout, semantic, effective_config_value, options)
345        }
346        LayoutDiagram::TreemapDiagram(layout) => {
347            render_treemap_diagram_svg(layout, semantic, effective_config_value, options)
348        }
349        LayoutDiagram::VennDiagram(layout) => {
350            render_venn_diagram_svg(layout, semantic, effective_config_value, title, options)
351        }
352        LayoutDiagram::XyChartDiagram(layout) => {
353            render_xychart_diagram_svg(layout, semantic, effective_config_value, options)
354        }
355        LayoutDiagram::QuadrantChartDiagram(layout) => {
356            render_quadrantchart_diagram_svg(layout, semantic, effective_config_value, options)
357        }
358        LayoutDiagram::FlowchartV2(layout) => render_flowchart_v2_svg_with_config(
359            layout,
360            semantic,
361            effective_config,
362            title,
363            measurer,
364            options,
365        ),
366        LayoutDiagram::StateDiagramV2(layout) => render_state_diagram_v2_svg(
367            layout,
368            semantic,
369            effective_config_value,
370            title,
371            measurer,
372            options,
373        ),
374        LayoutDiagram::ClassDiagramV2(layout) => {
375            let model = crate::json::from_value_ref(semantic)?;
376            class::render_class_diagram_v2_svg_model_with_config(
377                layout,
378                &model,
379                effective_config,
380                title,
381                measurer,
382                options,
383            )
384        }
385        LayoutDiagram::ErDiagram(layout) => render_er_diagram_svg(
386            layout,
387            semantic,
388            effective_config_value,
389            title,
390            measurer,
391            options,
392        ),
393        LayoutDiagram::SequenceDiagram(layout) => {
394            sequence::render_sequence_diagram_svg_with_config(
395                layout,
396                semantic,
397                effective_config,
398                title,
399                measurer,
400                options,
401            )
402        }
403        LayoutDiagram::InfoDiagram(layout) => {
404            render_info_diagram_svg(layout, semantic, effective_config_value, options)
405        }
406        LayoutDiagram::PacketDiagram(layout) => {
407            render_packet_diagram_svg(layout, semantic, effective_config_value, title, options)
408        }
409        LayoutDiagram::TimelineDiagram(layout) => render_timeline_diagram_svg(
410            layout,
411            semantic,
412            effective_config_value,
413            title,
414            measurer,
415            options,
416        ),
417        LayoutDiagram::PieDiagram(layout) => {
418            render_pie_diagram_svg(layout, semantic, effective_config_value, options)
419        }
420        LayoutDiagram::JourneyDiagram(layout) => render_journey_diagram_svg(
421            layout,
422            semantic,
423            effective_config_value,
424            title,
425            measurer,
426            options,
427        ),
428        LayoutDiagram::KanbanDiagram(layout) => {
429            render_kanban_diagram_svg(layout, semantic, effective_config_value, options)
430        }
431        LayoutDiagram::GitGraphDiagram(layout) => render_gitgraph_diagram_svg(
432            layout,
433            semantic,
434            effective_config_value,
435            title,
436            measurer,
437            options,
438        ),
439        LayoutDiagram::GanttDiagram(layout) => {
440            render_gantt_diagram_svg(layout, semantic, effective_config_value, options)
441        }
442        LayoutDiagram::TreeViewDiagram(layout) => {
443            render_tree_view_diagram_svg(layout, semantic, effective_config_value, options)
444        }
445        LayoutDiagram::IshikawaDiagram(layout) => {
446            render_ishikawa_diagram_svg(layout, semantic, effective_config_value, options)
447        }
448        LayoutDiagram::EventModelingDiagram(layout) => {
449            render_eventmodeling_diagram_svg(layout, semantic, effective_config_value, options)
450        }
451        LayoutDiagram::C4Diagram(layout) => render_c4_diagram_svg(
452            layout,
453            semantic,
454            effective_config_value,
455            title,
456            measurer,
457            options,
458        ),
459    }
460}
461
462pub fn render_layout_svg_parts_for_render_model_with_config(
463    layout: &crate::model::LayoutDiagram,
464    semantic: &merman_core::RenderSemanticModel,
465    effective_config: &merman_core::MermaidConfig,
466    title: Option<&str>,
467    measurer: &dyn TextMeasurer,
468    options: &SvgRenderOptions,
469) -> Result<String> {
470    let svg = render_layout_svg_parts_for_render_model_with_config_raw(
471        layout,
472        semantic,
473        effective_config,
474        title,
475        measurer,
476        options,
477    )?;
478    apply_theme_css(svg, effective_config.as_value())
479}
480
481fn render_layout_svg_parts_for_render_model_with_config_raw(
482    layout: &crate::model::LayoutDiagram,
483    semantic: &merman_core::RenderSemanticModel,
484    effective_config: &merman_core::MermaidConfig,
485    title: Option<&str>,
486    measurer: &dyn TextMeasurer,
487    options: &SvgRenderOptions,
488) -> Result<String> {
489    use crate::model::LayoutDiagram;
490    use merman_core::RenderSemanticModel;
491
492    match (layout, semantic) {
493        (LayoutDiagram::ArchitectureDiagram(layout), RenderSemanticModel::Architecture(model)) => {
494            architecture::render_architecture_diagram_svg_typed_with_config(
495                layout,
496                model,
497                effective_config,
498                options,
499            )
500        }
501        (LayoutDiagram::FlowchartV2(layout), RenderSemanticModel::Flowchart(model)) => {
502            render_flowchart_v2_svg_model_with_config(
503                layout,
504                model,
505                effective_config,
506                title,
507                measurer,
508                options,
509            )
510        }
511        (LayoutDiagram::MindmapDiagram(layout), RenderSemanticModel::Mindmap(model)) => {
512            mindmap::render_mindmap_diagram_svg_model_with_config(
513                layout,
514                model,
515                effective_config,
516                options,
517            )
518        }
519        (LayoutDiagram::StateDiagramV2(layout), RenderSemanticModel::State(model)) => {
520            state::render_state_diagram_v2_svg_model(
521                layout,
522                model,
523                effective_config.as_value(),
524                title,
525                measurer,
526                options,
527            )
528        }
529        (LayoutDiagram::ClassDiagramV2(layout), RenderSemanticModel::Class(model)) => {
530            class::render_class_diagram_v2_svg_model_with_config(
531                layout,
532                model,
533                effective_config,
534                title,
535                measurer,
536                options,
537            )
538        }
539        (LayoutDiagram::SequenceDiagram(layout), RenderSemanticModel::Sequence(model)) => {
540            sequence::render_sequence_diagram_svg_model_with_config(
541                layout,
542                model,
543                effective_config,
544                title,
545                measurer,
546                options,
547            )
548        }
549        (LayoutDiagram::KanbanDiagram(layout), RenderSemanticModel::Kanban(_)) => {
550            render_kanban_diagram_svg(
551                layout,
552                &serde_json::Value::Null,
553                effective_config.as_value(),
554                options,
555            )
556        }
557        (LayoutDiagram::GanttDiagram(layout), RenderSemanticModel::Gantt(model)) => {
558            gantt::render_gantt_diagram_svg_model(
559                layout,
560                model,
561                effective_config.as_value(),
562                options,
563            )
564        }
565        (LayoutDiagram::PieDiagram(layout), RenderSemanticModel::Pie(model)) => {
566            pie::render_pie_diagram_svg_model(layout, model, effective_config.as_value(), options)
567        }
568        (LayoutDiagram::PacketDiagram(layout), RenderSemanticModel::Packet(model)) => {
569            packet::render_packet_diagram_svg_model(
570                layout,
571                model,
572                effective_config.as_value(),
573                title,
574                options,
575            )
576        }
577        (LayoutDiagram::TimelineDiagram(layout), RenderSemanticModel::Timeline(model)) => {
578            timeline::render_timeline_diagram_svg_model(
579                layout,
580                model,
581                effective_config.as_value(),
582                title,
583                measurer,
584                options,
585            )
586        }
587        (LayoutDiagram::JourneyDiagram(layout), RenderSemanticModel::Journey(model)) => {
588            journey::render_journey_diagram_svg_model(
589                layout,
590                model,
591                effective_config.as_value(),
592                title,
593                measurer,
594                options,
595            )
596        }
597        (LayoutDiagram::RequirementDiagram(layout), RenderSemanticModel::Requirement(model)) => {
598            requirement::render_requirement_diagram_svg_model(
599                layout,
600                model,
601                effective_config.as_value(),
602                title,
603                options,
604            )
605        }
606        (LayoutDiagram::SankeyDiagram(layout), RenderSemanticModel::Sankey(_)) => {
607            render_sankey_diagram_svg(
608                layout,
609                &serde_json::Value::Null,
610                effective_config.as_value(),
611                options,
612            )
613        }
614        (LayoutDiagram::RadarDiagram(layout), RenderSemanticModel::Radar(model)) => {
615            radar::render_radar_diagram_svg_model(
616                layout,
617                model,
618                effective_config.as_value(),
619                options,
620            )
621        }
622        (LayoutDiagram::InfoDiagram(layout), RenderSemanticModel::Info(_)) => {
623            render_info_diagram_svg(
624                layout,
625                &serde_json::Value::Null,
626                effective_config.as_value(),
627                options,
628            )
629        }
630        (LayoutDiagram::TreemapDiagram(layout), RenderSemanticModel::Treemap(_)) => {
631            render_treemap_diagram_svg(
632                layout,
633                &serde_json::Value::Null,
634                effective_config.as_value(),
635                options,
636            )
637        }
638        (LayoutDiagram::VennDiagram(layout), RenderSemanticModel::Venn(model)) => {
639            venn::render_venn_diagram_svg_model(
640                layout,
641                model,
642                effective_config.as_value(),
643                title,
644                options,
645            )
646        }
647        (LayoutDiagram::BlockDiagram(layout), RenderSemanticModel::Block(model)) => {
648            render_block_diagram_svg_model(layout, model, effective_config.as_value(), options)
649        }
650        (LayoutDiagram::ErDiagram(layout), RenderSemanticModel::Er(model)) => {
651            render_er_diagram_svg_model(
652                layout,
653                model,
654                effective_config.as_value(),
655                title,
656                measurer,
657                options,
658            )
659        }
660        (LayoutDiagram::QuadrantChartDiagram(layout), RenderSemanticModel::QuadrantChart(_)) => {
661            render_quadrantchart_diagram_svg(
662                layout,
663                &serde_json::Value::Null,
664                effective_config.as_value(),
665                options,
666            )
667        }
668        (LayoutDiagram::XyChartDiagram(layout), RenderSemanticModel::XyChart(_)) => {
669            render_xychart_diagram_svg(
670                layout,
671                &serde_json::Value::Null,
672                effective_config.as_value(),
673                options,
674            )
675        }
676        (LayoutDiagram::GitGraphDiagram(layout), RenderSemanticModel::GitGraph(model)) => {
677            gitgraph::render_gitgraph_diagram_svg_model(
678                layout,
679                model,
680                effective_config.as_value(),
681                title,
682                measurer,
683                options,
684            )
685        }
686        (LayoutDiagram::TreeViewDiagram(layout), RenderSemanticModel::TreeView(model)) => {
687            tree_view::render_tree_view_diagram_svg_model(
688                layout,
689                model,
690                effective_config.as_value(),
691                options,
692            )
693        }
694        (LayoutDiagram::IshikawaDiagram(layout), RenderSemanticModel::Ishikawa(_)) => {
695            render_ishikawa_diagram_svg(
696                layout,
697                &serde_json::Value::Null,
698                effective_config.as_value(),
699                options,
700            )
701        }
702        (LayoutDiagram::EventModelingDiagram(layout), RenderSemanticModel::EventModeling(_)) => {
703            render_eventmodeling_diagram_svg(
704                layout,
705                &serde_json::Value::Null,
706                effective_config.as_value(),
707                options,
708            )
709        }
710        (LayoutDiagram::C4Diagram(layout), RenderSemanticModel::C4(model)) => {
711            c4::render_c4_diagram_svg_typed(
712                layout,
713                model,
714                effective_config.as_value(),
715                title,
716                measurer,
717                options,
718            )
719        }
720        (_, RenderSemanticModel::Json(semantic)) => render_layout_svg_parts_with_config_raw(
721            layout,
722            semantic,
723            effective_config,
724            title,
725            measurer,
726            options,
727        ),
728        _ => Err(Error::InvalidModel {
729            message: "semantic model does not match layout diagram type".to_string(),
730        }),
731    }
732}
733
734fn apply_theme_css(svg: String, effective_config: &serde_json::Value) -> Result<String> {
735    let Some(theme_css) = effective_config
736        .get("themeCSS")
737        .and_then(serde_json::Value::as_str)
738        .filter(|css| !css.trim().is_empty())
739    else {
740        return Ok(svg);
741    };
742
743    let metadata = SvgPostprocessMetadata::from_svg(&svg);
744    let pipeline = SvgPipeline::parity().with_postprocessor(ScopedCssPostprocessor::new(theme_css));
745    pipeline.process_to_string_with_metadata(&svg, &metadata)
746}
747
748pub fn render_flowchart_v2_debug_svg(
749    layout: &FlowchartV2Layout,
750    options: &SvgRenderOptions,
751) -> String {
752    flowchart::render_flowchart_v2_debug_svg(layout, options)
753}
754
755pub fn render_sequence_diagram_debug_svg(
756    layout: &SequenceDiagramLayout,
757    options: &SvgRenderOptions,
758) -> String {
759    sequence::render_sequence_diagram_debug_svg(layout, options)
760}
761
762pub fn render_sequence_diagram_svg(
763    layout: &SequenceDiagramLayout,
764    semantic: &serde_json::Value,
765    effective_config: &serde_json::Value,
766    diagram_title: Option<&str>,
767    measurer: &dyn TextMeasurer,
768    options: &SvgRenderOptions,
769) -> Result<String> {
770    sequence::render_sequence_diagram_svg(
771        layout,
772        semantic,
773        effective_config,
774        diagram_title,
775        measurer,
776        options,
777    )
778}
779
780pub fn render_error_diagram_svg(
781    layout: &ErrorDiagramLayout,
782    _semantic: &serde_json::Value,
783    _effective_config: &serde_json::Value,
784    options: &SvgRenderOptions,
785) -> Result<String> {
786    error::render_error_diagram_svg(layout, _semantic, _effective_config, options)
787}
788
789pub fn render_info_diagram_svg(
790    layout: &InfoDiagramLayout,
791    _semantic: &serde_json::Value,
792    _effective_config: &serde_json::Value,
793    options: &SvgRenderOptions,
794) -> Result<String> {
795    info::render_info_diagram_svg(layout, _semantic, _effective_config, options)
796}
797
798pub fn render_pie_diagram_svg(
799    layout: &PieDiagramLayout,
800    semantic: &serde_json::Value,
801    _effective_config: &serde_json::Value,
802    options: &SvgRenderOptions,
803) -> Result<String> {
804    pie::render_pie_diagram_svg(layout, semantic, _effective_config, options)
805}
806
807pub fn render_requirement_diagram_svg(
808    layout: &RequirementDiagramLayout,
809    semantic: &serde_json::Value,
810    effective_config: &serde_json::Value,
811    diagram_title: Option<&str>,
812    options: &SvgRenderOptions,
813) -> Result<String> {
814    requirement::render_requirement_diagram_svg(
815        layout,
816        semantic,
817        effective_config,
818        diagram_title,
819        options,
820    )
821}
822
823pub fn render_block_diagram_svg(
824    layout: &BlockDiagramLayout,
825    semantic: &serde_json::Value,
826    effective_config: &serde_json::Value,
827    options: &SvgRenderOptions,
828) -> Result<String> {
829    block::render_block_diagram_svg(layout, semantic, effective_config, options)
830}
831
832pub fn render_block_diagram_svg_model(
833    layout: &BlockDiagramLayout,
834    model: &merman_core::diagrams::block::BlockDiagramRenderModel,
835    effective_config: &serde_json::Value,
836    options: &SvgRenderOptions,
837) -> Result<String> {
838    block::render_block_diagram_svg_model(layout, model, effective_config, options)
839}
840
841pub fn render_er_diagram_svg_model(
842    layout: &ErDiagramLayout,
843    model: &merman_core::diagrams::er::ErDiagramRenderModel,
844    effective_config: &serde_json::Value,
845    diagram_title: Option<&str>,
846    measurer: &dyn TextMeasurer,
847    options: &SvgRenderOptions,
848) -> Result<String> {
849    er::render_er_diagram_svg_model(
850        layout,
851        model,
852        effective_config,
853        diagram_title,
854        measurer,
855        options,
856    )
857}
858
859pub fn render_radar_diagram_svg(
860    layout: &RadarDiagramLayout,
861    semantic: &serde_json::Value,
862    effective_config: &serde_json::Value,
863    options: &SvgRenderOptions,
864) -> Result<String> {
865    radar::render_radar_diagram_svg(layout, semantic, effective_config, options)
866}
867
868pub fn render_quadrantchart_diagram_svg(
869    layout: &QuadrantChartDiagramLayout,
870    _semantic: &serde_json::Value,
871    _effective_config: &serde_json::Value,
872    options: &SvgRenderOptions,
873) -> Result<String> {
874    quadrantchart::render_quadrantchart_diagram_svg(layout, _semantic, _effective_config, options)
875}
876
877pub fn render_xychart_diagram_svg(
878    layout: &XyChartDiagramLayout,
879    _semantic: &serde_json::Value,
880    _effective_config: &serde_json::Value,
881    options: &SvgRenderOptions,
882) -> Result<String> {
883    xychart::render_xychart_diagram_svg(layout, _semantic, _effective_config, options)
884}
885
886pub fn render_treemap_diagram_svg(
887    layout: &crate::model::TreemapDiagramLayout,
888    _semantic: &serde_json::Value,
889    effective_config: &serde_json::Value,
890    options: &SvgRenderOptions,
891) -> Result<String> {
892    treemap::render_treemap_diagram_svg(layout, _semantic, effective_config, options)
893}
894
895pub fn render_venn_diagram_svg(
896    layout: &VennDiagramLayout,
897    semantic: &serde_json::Value,
898    effective_config: &serde_json::Value,
899    diagram_title: Option<&str>,
900    options: &SvgRenderOptions,
901) -> Result<String> {
902    venn::render_venn_diagram_svg(layout, semantic, effective_config, diagram_title, options)
903}
904
905pub fn render_venn_diagram_svg_model(
906    layout: &VennDiagramLayout,
907    model: &merman_core::diagrams::venn::VennDiagramRenderModel,
908    effective_config: &serde_json::Value,
909    diagram_title: Option<&str>,
910    options: &SvgRenderOptions,
911) -> Result<String> {
912    venn::render_venn_diagram_svg_model(layout, model, effective_config, diagram_title, options)
913}
914
915pub fn render_packet_diagram_svg(
916    layout: &PacketDiagramLayout,
917    semantic: &serde_json::Value,
918    _effective_config: &serde_json::Value,
919    diagram_title: Option<&str>,
920    options: &SvgRenderOptions,
921) -> Result<String> {
922    packet::render_packet_diagram_svg(layout, semantic, _effective_config, diagram_title, options)
923}
924
925pub fn render_timeline_diagram_svg(
926    layout: &TimelineDiagramLayout,
927    semantic: &serde_json::Value,
928    effective_config: &serde_json::Value,
929    _diagram_title: Option<&str>,
930    _measurer: &dyn TextMeasurer,
931    options: &SvgRenderOptions,
932) -> Result<String> {
933    timeline::render_timeline_diagram_svg(
934        layout,
935        semantic,
936        effective_config,
937        _diagram_title,
938        _measurer,
939        options,
940    )
941}
942
943pub fn render_journey_diagram_svg(
944    layout: &crate::model::JourneyDiagramLayout,
945    semantic: &serde_json::Value,
946    effective_config: &serde_json::Value,
947    _diagram_title: Option<&str>,
948    _measurer: &dyn TextMeasurer,
949    options: &SvgRenderOptions,
950) -> Result<String> {
951    journey::render_journey_diagram_svg(
952        layout,
953        semantic,
954        effective_config,
955        _diagram_title,
956        _measurer,
957        options,
958    )
959}
960
961pub fn render_kanban_diagram_svg(
962    layout: &crate::model::KanbanDiagramLayout,
963    _semantic: &serde_json::Value,
964    _effective_config: &serde_json::Value,
965    options: &SvgRenderOptions,
966) -> Result<String> {
967    kanban::render_kanban_diagram_svg(layout, _semantic, _effective_config, options)
968}
969
970pub fn render_gitgraph_diagram_svg(
971    layout: &crate::model::GitGraphDiagramLayout,
972    semantic: &serde_json::Value,
973    _effective_config: &serde_json::Value,
974    diagram_title: Option<&str>,
975    measurer: &dyn TextMeasurer,
976    options: &SvgRenderOptions,
977) -> Result<String> {
978    gitgraph::render_gitgraph_diagram_svg(
979        layout,
980        semantic,
981        _effective_config,
982        diagram_title,
983        measurer,
984        options,
985    )
986}
987
988pub fn render_tree_view_diagram_svg(
989    layout: &TreeViewDiagramLayout,
990    semantic: &serde_json::Value,
991    effective_config: &serde_json::Value,
992    options: &SvgRenderOptions,
993) -> Result<String> {
994    tree_view::render_tree_view_diagram_svg(layout, semantic, effective_config, options)
995}
996
997pub fn render_ishikawa_diagram_svg(
998    layout: &IshikawaDiagramLayout,
999    semantic: &serde_json::Value,
1000    effective_config: &serde_json::Value,
1001    options: &SvgRenderOptions,
1002) -> Result<String> {
1003    ishikawa::render_ishikawa_diagram_svg(layout, semantic, effective_config, options)
1004}
1005
1006pub fn render_eventmodeling_diagram_svg(
1007    layout: &EventModelingDiagramLayout,
1008    semantic: &serde_json::Value,
1009    effective_config: &serde_json::Value,
1010    options: &SvgRenderOptions,
1011) -> Result<String> {
1012    eventmodeling::render_eventmodeling_diagram_svg(layout, semantic, effective_config, options)
1013}
1014
1015pub fn render_gantt_diagram_svg(
1016    layout: &crate::model::GanttDiagramLayout,
1017    semantic: &serde_json::Value,
1018    _effective_config: &serde_json::Value,
1019    options: &SvgRenderOptions,
1020) -> Result<String> {
1021    gantt::render_gantt_diagram_svg(layout, semantic, _effective_config, options)
1022}
1023
1024pub fn render_mindmap_diagram_svg(
1025    layout: &MindmapDiagramLayout,
1026    semantic: &serde_json::Value,
1027    _effective_config: &serde_json::Value,
1028    options: &SvgRenderOptions,
1029) -> Result<String> {
1030    mindmap::render_mindmap_diagram_svg(layout, semantic, _effective_config, options)
1031}
1032
1033pub fn render_mindmap_diagram_svg_with_config(
1034    layout: &MindmapDiagramLayout,
1035    semantic: &serde_json::Value,
1036    effective_config: &merman_core::MermaidConfig,
1037    options: &SvgRenderOptions,
1038) -> Result<String> {
1039    mindmap::render_mindmap_diagram_svg_with_config(layout, semantic, effective_config, options)
1040}
1041
1042pub fn render_architecture_diagram_svg(
1043    layout: &ArchitectureDiagramLayout,
1044    semantic: &serde_json::Value,
1045    effective_config: &serde_json::Value,
1046    options: &SvgRenderOptions,
1047) -> Result<String> {
1048    architecture::render_architecture_diagram_svg(layout, semantic, effective_config, options)
1049}
1050
1051pub fn render_c4_diagram_svg(
1052    layout: &crate::model::C4DiagramLayout,
1053    semantic: &serde_json::Value,
1054    effective_config: &serde_json::Value,
1055    diagram_title: Option<&str>,
1056    _measurer: &dyn TextMeasurer,
1057    options: &SvgRenderOptions,
1058) -> Result<String> {
1059    c4::render_c4_diagram_svg(
1060        layout,
1061        semantic,
1062        effective_config,
1063        diagram_title,
1064        _measurer,
1065        options,
1066    )
1067}
1068
1069pub fn render_flowchart_v2_svg(
1070    layout: &FlowchartV2Layout,
1071    semantic: &serde_json::Value,
1072    effective_config: &serde_json::Value,
1073    diagram_title: Option<&str>,
1074    measurer: &dyn TextMeasurer,
1075    options: &SvgRenderOptions,
1076) -> Result<String> {
1077    flowchart::render_flowchart_v2_svg(
1078        layout,
1079        semantic,
1080        effective_config,
1081        diagram_title,
1082        measurer,
1083        options,
1084    )
1085}
1086
1087pub fn render_flowchart_v2_svg_with_config(
1088    layout: &FlowchartV2Layout,
1089    semantic: &serde_json::Value,
1090    effective_config: &merman_core::MermaidConfig,
1091    diagram_title: Option<&str>,
1092    measurer: &dyn TextMeasurer,
1093    options: &SvgRenderOptions,
1094) -> Result<String> {
1095    flowchart::render_flowchart_v2_svg_with_config(
1096        layout,
1097        semantic,
1098        effective_config,
1099        diagram_title,
1100        measurer,
1101        options,
1102    )
1103}
1104
1105pub fn render_flowchart_v2_svg_model_with_config(
1106    layout: &FlowchartV2Layout,
1107    model: &merman_core::diagrams::flowchart::FlowchartV2Model,
1108    effective_config: &merman_core::MermaidConfig,
1109    diagram_title: Option<&str>,
1110    measurer: &dyn TextMeasurer,
1111    options: &SvgRenderOptions,
1112) -> Result<String> {
1113    flowchart::render_flowchart_v2_svg_model_with_config(
1114        layout,
1115        model,
1116        effective_config,
1117        diagram_title,
1118        measurer,
1119        options,
1120    )
1121}
1122
1123pub fn render_state_diagram_v2_svg(
1124    layout: &StateDiagramV2Layout,
1125    semantic: &serde_json::Value,
1126    effective_config: &serde_json::Value,
1127    diagram_title: Option<&str>,
1128    measurer: &dyn TextMeasurer,
1129    options: &SvgRenderOptions,
1130) -> Result<String> {
1131    state::render_state_diagram_v2_svg(
1132        layout,
1133        semantic,
1134        effective_config,
1135        diagram_title,
1136        measurer,
1137        options,
1138    )
1139}
1140
1141pub fn render_state_diagram_v2_debug_svg(
1142    layout: &StateDiagramV2Layout,
1143    options: &SvgRenderOptions,
1144) -> String {
1145    state::render_state_diagram_v2_debug_svg(layout, options)
1146}
1147
1148pub fn render_class_diagram_v2_debug_svg(
1149    layout: &ClassDiagramV2Layout,
1150    options: &SvgRenderOptions,
1151) -> String {
1152    class::render_class_diagram_v2_debug_svg(layout, options)
1153}
1154
1155pub fn render_class_diagram_v2_svg(
1156    layout: &ClassDiagramV2Layout,
1157    semantic: &serde_json::Value,
1158    effective_config: &serde_json::Value,
1159    diagram_title: Option<&str>,
1160    measurer: &dyn TextMeasurer,
1161    options: &SvgRenderOptions,
1162) -> Result<String> {
1163    class::render_class_diagram_v2_svg(
1164        layout,
1165        semantic,
1166        effective_config,
1167        diagram_title,
1168        measurer,
1169        options,
1170    )
1171}
1172
1173pub fn render_er_diagram_debug_svg(layout: &ErDiagramLayout, options: &SvgRenderOptions) -> String {
1174    er::render_er_diagram_debug_svg(layout, options)
1175}
1176
1177pub fn render_er_diagram_svg(
1178    layout: &ErDiagramLayout,
1179    semantic: &serde_json::Value,
1180    effective_config: &serde_json::Value,
1181    diagram_title: Option<&str>,
1182    measurer: &dyn TextMeasurer,
1183    options: &SvgRenderOptions,
1184) -> Result<String> {
1185    er::render_er_diagram_svg(
1186        layout,
1187        semantic,
1188        effective_config,
1189        diagram_title,
1190        measurer,
1191        options,
1192    )
1193}
1194
1195pub fn render_sankey_diagram_svg(
1196    layout: &SankeyDiagramLayout,
1197    _semantic: &serde_json::Value,
1198    effective_config: &serde_json::Value,
1199    options: &SvgRenderOptions,
1200) -> Result<String> {
1201    sankey::render_sankey_diagram_svg(layout, _semantic, effective_config, options)
1202}
1203
1204// Ported from D3 `curveBasis` (d3-shape v3.x), used by Mermaid ER renderer `@11.12.2`.
1205fn curve_basis_path_d(points: &[crate::model::LayoutPoint]) -> String {
1206    curve::curve_basis_path_d(points)
1207}
1208fn render_node(out: &mut String, n: &LayoutNode) {
1209    layout_debug::render_node(out, n)
1210}
1211
1212fn render_state_node(out: &mut String, n: &LayoutNode) {
1213    layout_debug::render_state_node(out, n)
1214}
1215
1216fn render_cluster(out: &mut String, c: &LayoutCluster, include_markers: bool) {
1217    layout_debug::render_cluster(out, c, include_markers)
1218}
1219
1220fn compute_layout_bounds(
1221    clusters: &[LayoutCluster],
1222    nodes: &[LayoutNode],
1223    edges: &[crate::model::LayoutEdge],
1224) -> Option<Bounds> {
1225    layout_debug::compute_layout_bounds(clusters, nodes, edges)
1226}