hugr_core/hugr/views/
render.rs

1//! Helper methods to compute the node/edge/port style when rendering a HUGR
2//! into dot or mermaid format.
3
4use portgraph::render::{EdgeStyle, NodeStyle, PortStyle};
5use portgraph::{LinkView, NodeIndex, PortIndex, PortView};
6
7use crate::ops::{NamedOp, OpType};
8use crate::types::EdgeKind;
9use crate::HugrView;
10
11/// Configuration for rendering a HUGR graph.
12#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
13#[non_exhaustive]
14pub struct RenderConfig {
15    /// Show the node index in the graph nodes.
16    pub node_indices: bool,
17    /// Show port offsets in the graph edges.
18    pub port_offsets_in_edges: bool,
19    /// Show type labels on edges.
20    pub type_labels_in_edges: bool,
21}
22
23impl Default for RenderConfig {
24    fn default() -> Self {
25        Self {
26            node_indices: true,
27            port_offsets_in_edges: true,
28            type_labels_in_edges: true,
29        }
30    }
31}
32
33/// Formatter method to compute a node style.
34pub(super) fn node_style<H: HugrView + ?Sized>(
35    h: &H,
36    config: RenderConfig,
37) -> Box<dyn FnMut(NodeIndex) -> NodeStyle + '_> {
38    fn node_name<H: HugrView + ?Sized>(h: &H, n: NodeIndex) -> String {
39        match h.get_optype(h.get_node(n)) {
40            OpType::FuncDecl(f) => format!("FuncDecl: \"{}\"", f.name),
41            OpType::FuncDefn(f) => format!("FuncDefn: \"{}\"", f.name),
42            op => op.name().to_string(),
43        }
44    }
45
46    if config.node_indices {
47        Box::new(move |n| {
48            NodeStyle::Box(format!(
49                "({ni}) {name}",
50                ni = n.index(),
51                name = node_name(h, n)
52            ))
53        })
54    } else {
55        Box::new(move |n| NodeStyle::Box(node_name(h, n)))
56    }
57}
58
59/// Formatter method to compute a port style.
60pub(super) fn port_style<H: HugrView + ?Sized>(
61    h: &H,
62    _config: RenderConfig,
63) -> Box<dyn FnMut(PortIndex) -> PortStyle + '_> {
64    let graph = h.portgraph();
65    Box::new(move |port| {
66        let node = graph.port_node(port).unwrap();
67        let optype = h.get_optype(h.get_node(node));
68        let offset = graph.port_offset(port).unwrap();
69        match optype.port_kind(offset).unwrap() {
70            EdgeKind::Function(pf) => PortStyle::new(html_escape::encode_text(&format!("{}", pf))),
71            EdgeKind::Const(ty) | EdgeKind::Value(ty) => {
72                PortStyle::new(html_escape::encode_text(&format!("{}", ty)))
73            }
74            EdgeKind::StateOrder => match graph.port_links(port).count() > 0 {
75                true => PortStyle::text("", false),
76                false => PortStyle::Hidden,
77            },
78            _ => PortStyle::text("", true),
79        }
80    })
81}
82
83/// Formatter method to compute an edge style.
84#[allow(clippy::type_complexity)]
85pub(super) fn edge_style<H: HugrView + ?Sized>(
86    h: &H,
87    config: RenderConfig,
88) -> Box<
89    dyn FnMut(
90            <H::Portgraph<'_> as LinkView>::LinkEndpoint,
91            <H::Portgraph<'_> as LinkView>::LinkEndpoint,
92        ) -> EdgeStyle
93        + '_,
94> {
95    let graph = h.portgraph();
96    Box::new(move |src, tgt| {
97        let src_node = graph.port_node(src).unwrap();
98        let src_optype = h.get_optype(h.get_node(src_node));
99        let src_offset = graph.port_offset(src).unwrap();
100        let tgt_offset = graph.port_offset(tgt).unwrap();
101
102        let port_kind = src_optype.port_kind(src_offset).unwrap();
103
104        // StateOrder edges: Dotted line.
105        // Control flow edges: Dashed line.
106        // Static and Value edges: Solid line with label.
107        let style = match port_kind {
108            EdgeKind::StateOrder => EdgeStyle::Dotted,
109            EdgeKind::ControlFlow => EdgeStyle::Dashed,
110            EdgeKind::Const(_) | EdgeKind::Function(_) | EdgeKind::Value(_) => EdgeStyle::Solid,
111        };
112
113        // Compute the label for the edge, given the setting flags.
114        fn type_label(e: EdgeKind) -> Option<String> {
115            match e {
116                EdgeKind::Const(ty) | EdgeKind::Value(ty) => Some(format!("{}", ty)),
117                EdgeKind::Function(pf) => Some(format!("{}", pf)),
118                _ => None,
119            }
120        }
121        //
122        // Only static and value edges have types to display.
123        let label = match (
124            config.port_offsets_in_edges,
125            type_label(port_kind).filter(|_| config.type_labels_in_edges),
126        ) {
127            (true, Some(ty)) => {
128                format!("{}:{}\n{ty}", src_offset.index(), tgt_offset.index())
129            }
130            (true, _) => format!("{}:{}", src_offset.index(), tgt_offset.index()),
131            (false, Some(ty)) => ty.to_string(),
132            _ => return style,
133        };
134        style.with_label(label)
135    })
136}