1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
use super::writer::{Quote, Statement};

/// Structs that implement [`Attributes`] are used for writing the attributes
/// of a diagraph, graph, subgraph, subgraph cluster, node or edge.
///
/// The raw [`Attributes::set`] function is provided for setting arbitary atrributes,
/// but it is recommended to use the more typesafe functions where available.
pub trait Attributes: Sized {
    /// Sets an attribute. See the Graphviz [documentation](https://graphviz.org/doc/info/attrs.html)
    /// for a full list of available names and values.
    /// Set the arguement `quote` to true if the `value` should be written in quotes `"`, to escape
    /// any special characters. Not that any quote in the string need to be escaped before calling.
    fn set(&mut self, name: &str, value: &str, quote: bool) -> &mut Self;

    // /Set the name of the font family
    fn set_font(&mut self, label: &str) -> &mut Self {
        self.set("fontname", label, true)
    }

    /// Set arbitary html, useful for constructing more complex nodes
    fn set_html(&mut self, label: &str) -> &mut Self {
        self.set("label", label, false)
    }

    /// Set the display label for a graph, node or edge
    fn set_label(&mut self, label: &str) -> &mut Self {
        self.set("label", label, true)
    }

    /// Set the label to appear at the head of an edge
    fn set_head_label(&mut self, label: &str) -> &mut Self {
        self.set("headlabel", label, true)
    }

    /// Set the label to appear at the tail of an edge
    fn set_tail_label(&mut self, label: &str) -> &mut Self {
        self.set("taillabel", label, true)
    }

    /// Set the edge or line color
    fn set_color(&mut self, color: Color) -> &mut Self {
        self.set("color", color.as_str(), false)
    }

    /// Set the color to fill the are with
    fn set_fill_color(&mut self, color: Color) -> &mut Self {
        self.set("fillcolor", color.as_str(), false)
    }

    /// Set the color of the font
    fn set_font_color(&mut self, color: Color) -> &mut Self {
        self.set("fontcolor", color.as_str(), false)
    }

    /// Set the background color
    fn set_background_color(&mut self, color: Color) -> &mut Self {
        self.set("bgcolor", color.as_str(), false)
    }

    /// Set the shape of a graph, subgraph, cluster or node
    fn set_shape(&mut self, shape: Shape) -> &mut Self {
        self.set("shape", shape.as_str(), false)
    }

    /// Set the style
    fn set_style(&mut self, style: Style) -> &mut Self {
        self.set("style", style.as_str(), true)
    }

    /// Set the relative rank
    fn set_rank(&mut self, rank: Rank) -> &mut Self {
        self.set("rank", rank.as_str(), false)
    }
}

/// An [`AttributesList`] sets the attributes of an edge or node.
/// See the [`Attributes`] trait for more information.
pub struct AttributesList<'d, 'w> {
    statement: Statement<'d, 'w>,
    at_least_one_set: bool,
}

impl<'d, 'w> AttributesList<'d, 'w> {
    pub(crate) fn new(statement: Statement<'d, 'w>) -> Self {
        Self {
            statement,
            at_least_one_set: false,
        }
    }
}

impl<'d, 'w> Attributes for AttributesList<'d, 'w> {
    fn set(&mut self, name: &str, value: &str, quote: bool) -> &mut Self {
        if !self.at_least_one_set {
            self.at_least_one_set = true;
            self.statement.write(b" [");
        } else {
            self.statement.write(b", ");
        }
        self.statement.write(name.as_bytes());
        self.statement.write(b"=");
        if quote {
            Quote::new(&mut self.statement).write(value.as_bytes());
        } else {
            self.statement.write(value.as_bytes());
        }
        self
    }
}

impl<'d, 'w> Drop for AttributesList<'d, 'w> {
    fn drop(&mut self) {
        if self.at_least_one_set {
            self.statement.write(b"]");
        }
    }
}

/// Shape of a component. This list is not comprehensive, the full list
/// is visible [here](https://graphviz.org/doc/info/shapes.html#polygon)
/// and can instead be set using [`Attributes::set`].
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Shape {
    Circle,
    Record,
    Rectangle,
    None,
    Mdiamond,
    Mrecord,
    Msquare,
}

impl Shape {
    fn as_str(&self) -> &str {
        match self {
            Self::Circle => "circle",
            Self::Record => "record",
            Self::Mrecord => "Mrecord",
            Self::Mdiamond => "Mdiamond",
            Self::Rectangle => "rectangle",
            Self::Msquare => "Msquare",
            Self::None => "none",
        }
    }
}

/// Color of a line or fill. This list is far from comprehensive, the
/// full list is visible [here](https://graphviz.org/doc/info/colors.html),
/// and can instead be set using [`Attributes::set`]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Color {
    Black,
    Grey,
    LightGrey,
    PaleGreen,
    PaleTurquoise,
    Red,
    White,
    Blue,
}

impl Color {
    pub fn as_str(&self) -> &str {
        match self {
            Self::Black => "black",
            Self::Grey => "gray",
            Self::LightGrey => "lightgray",
            Self::PaleGreen => "palegreen",
            Self::PaleTurquoise => "paleturquoise",
            Self::Red => "red",
            Self::White => "white",
            Self::Blue => "blue",
        }
    }
}

/// Style of design. Note that some of these or only valid for
/// certain combinations of node, edge and cluster. See documentation
/// [here](https://graphviz.org/docs/attr-types/style/) for more information.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Style {
    /// Nodes and edges
    Dashed,
    /// Nodes and edges
    Dotted,
    /// Nodes and edges
    Solid,
    /// Nodes and edges
    Invisible,
    /// Nodes and edges
    Bold,

    /// Edges only
    Tapered,

    /// Nodes only
    Wedged,
    /// Only for elliptically-shaped nodes
    Diagonals,

    /// Node and clusters
    Filled,
    /// Node and clusters
    Striped,
    /// Node and clusters
    Rounded,

    /// Any
    Radial,
    /// Any
    Unfilled,
}

impl Style {
    fn as_str(&self) -> &str {
        match self {
            Self::Dashed => "dashed",
            Self::Dotted => "dotted",
            Self::Solid => "solid",
            Self::Invisible => "invis",
            Self::Bold => "bold",

            Self::Tapered => "tapered",

            Self::Wedged => "wedged",
            Self::Diagonals => "diagonals",

            Self::Filled => "filled",
            Self::Striped => "striped",
            Self::Rounded => "rounded",

            Self::Radial => "radial",
            Self::Unfilled => "",
        }
    }
}

/// Node rank type, for more information see
/// [Graphviz documentation](https://graphviz.org/docs/attr-types/rankType/).
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Rank {
    Min,
    Same,
    Max,
    Source,
    Sink,
}

impl Rank {
    fn as_str(&self) -> &str {
        match self {
            Self::Source => "source",
            Self::Min => "min",
            Self::Same => "same",
            Self::Max => "max",
            Self::Sink => "sink",
        }
    }
}