Skip to main content

dotavious/attributes/
mod.rs

1// TODO: docs
2
3mod arrow_type;
4mod cluster_mode;
5mod color;
6mod compass_point;
7mod direction;
8mod image;
9mod label;
10mod ordering;
11mod output_mode;
12mod pack_mode;
13mod page_direction;
14mod point;
15mod port_position;
16mod rankdir;
17mod ratio;
18mod rectangle;
19mod shape;
20mod spline_type;
21mod splines;
22mod style;
23mod viewport;
24
25pub use crate::attributes::arrow_type::ArrowType;
26pub use crate::attributes::cluster_mode::ClusterMode;
27pub use crate::attributes::color::{Color, ColorList, IntoWeightedColor, WeightedColor};
28pub use crate::attributes::compass_point::CompassPoint;
29pub use crate::attributes::direction::Direction;
30pub use crate::attributes::image::{ImagePosition, ImageScale};
31pub use crate::attributes::label::{LabelJustification, LabelLocation};
32pub use crate::attributes::ordering::Ordering;
33pub use crate::attributes::output_mode::OutputMode;
34pub use crate::attributes::pack_mode::PackMode;
35pub use crate::attributes::page_direction::PageDirection;
36pub use crate::attributes::point::Point;
37pub use crate::attributes::port_position::PortPosition;
38pub use crate::attributes::rankdir::RankDir;
39pub use crate::attributes::ratio::Ratio;
40pub use crate::attributes::rectangle::Rectangle;
41pub use crate::attributes::shape::Shape;
42pub use crate::attributes::spline_type::SplineType;
43pub use crate::attributes::splines::Splines;
44pub use crate::attributes::style::{EdgeStyle, GraphStyle, NodeStyle, Styles};
45#[doc(hidden)]
46pub use crate::attributes::AttributeText::{AttrStr, EscStr, HtmlStr, QuotedStr};
47use crate::dot::DotString;
48use crate::validation::{ValidationError, ValidationResult};
49use indexmap::map::IndexMap;
50use std::borrow::Cow;
51use std::collections::HashMap;
52use Cow::Borrowed;
53use crate::attributes::viewport::ViewPort;
54
55/// The text for a graphviz label on a node or edge.
56#[derive(Clone, PartialEq, Eq, Debug)]
57pub enum AttributeText<'a> {
58    /// Preserves the text directly as is.
59    AttrStr(Cow<'a, str>),
60
61    /// This kind of label uses the graphviz label escString type:
62    /// <http://www.graphviz.org/doc/info/attrs.html#k:escString>
63    ///
64    /// Occurrences of backslashes (`\`) are not escaped; instead they
65    /// are interpreted as initiating an escString escape sequence.
66    ///
67    /// Escape sequences of particular interest: in addition to `\n`
68    /// to break a line (centering the line preceding the `\n`), there
69    /// are also the escape sequences `\l` which left-justifies the
70    /// preceding line and `\r` which right-justifies it.
71    EscStr(Cow<'a, str>),
72
73    /// This uses a graphviz [HTML string label][html].
74    /// The string is printed exactly as given, but between `<` and `>`.
75    /// **No escaping is performed.**
76    ///
77    /// [html]: https://graphviz.org/doc/info/shapes.html#html
78    HtmlStr(Cow<'a, str>),
79
80    /// Preserves the text directly as is but wrapped in quotes.
81    ///
82    /// Occurrences of backslashes (`\`) are escaped, and thus appear
83    /// as backslashes in the rendered label.
84    QuotedStr(Cow<'a, str>),
85}
86
87impl<'a> AttributeText<'a> {
88    pub fn attr<S: Into<Cow<'a, str>>>(s: S) -> AttributeText<'a> {
89        AttrStr(s.into())
90    }
91
92    pub fn escaped<S: Into<Cow<'a, str>>>(s: S) -> AttributeText<'a> {
93        EscStr(s.into())
94    }
95
96    pub fn html<S: Into<Cow<'a, str>>>(s: S) -> AttributeText<'a> {
97        HtmlStr(s.into())
98    }
99
100    pub fn quoted<S: Into<Cow<'a, str>>>(s: S) -> AttributeText<'a> {
101        QuotedStr(s.into())
102    }
103
104    fn escape_char<F>(c: char, mut f: F)
105    where
106        F: FnMut(char),
107    {
108        match c {
109            // not escaping \\, since Graphviz escString needs to
110            // interpret backslashes; see EscStr above.
111            '\\' => f(c),
112            _ => {
113                for c in c.escape_default() {
114                    f(c)
115                }
116            }
117        }
118    }
119
120    fn escape_str(s: &str) -> String {
121        let mut out = String::with_capacity(s.len());
122        for c in s.chars() {
123            AttributeText::escape_char(c, |c| out.push(c));
124        }
125        out
126    }
127
128    /// Renders text as string suitable for a attribute in a .dot file.
129    /// This includes quotes or suitable delimiters.
130    pub fn dot_string(&self) -> String {
131        match *self {
132            AttrStr(ref s) => format!("{}", s),
133            EscStr(ref s) => format!("\"{}\"", AttributeText::escape_str(&s)),
134            HtmlStr(ref s) => format!("<{}>", s),
135            QuotedStr(ref s) => format!("\"{}\"", escape_double_quotes(&s.to_string())),
136        }
137    }
138}
139
140impl<'a> From<ArrowType> for AttributeText<'a> {
141    fn from(arrow_type: ArrowType) -> Self {
142        AttributeText::attr(arrow_type.dot_string())
143    }
144}
145
146impl<'a> From<bool> for AttributeText<'a> {
147    fn from(v: bool) -> Self {
148        AttributeText::attr(v.to_string())
149    }
150}
151
152impl<'a> From<ClusterMode> for AttributeText<'a> {
153    fn from(mode: ClusterMode) -> Self {
154        AttributeText::quoted(mode.dot_string())
155    }
156}
157
158impl<'a> From<Color<'a>> for AttributeText<'a> {
159    fn from(color: Color<'a>) -> Self {
160        AttributeText::quoted(color.dot_string())
161    }
162}
163
164impl<'a> From<ColorList<'a>> for AttributeText<'a> {
165    fn from(color_list: ColorList<'a>) -> Self {
166        AttributeText::quoted(color_list.dot_string())
167    }
168}
169
170impl<'a> From<CompassPoint> for AttributeText<'a> {
171    fn from(compass: CompassPoint) -> Self {
172        AttributeText::quoted(compass.dot_string())
173    }
174}
175
176impl<'a> From<Direction> for AttributeText<'a> {
177    fn from(direction: Direction) -> Self {
178        AttributeText::attr(direction.dot_string())
179    }
180}
181
182impl<'a> From<EdgeStyle> for AttributeText<'a> {
183    fn from(style: EdgeStyle) -> Self {
184        AttributeText::attr(style.dot_string())
185    }
186}
187
188impl<'a> From<f32> for AttributeText<'a> {
189    fn from(v: f32) -> Self {
190        AttributeText::attr(v.to_string())
191    }
192}
193
194impl<'a> From<GraphStyle> for AttributeText<'a> {
195    fn from(style: GraphStyle) -> Self {
196        AttributeText::attr(style.dot_string())
197    }
198}
199
200impl<'a> From<ImagePosition> for AttributeText<'a> {
201    fn from(pos: ImagePosition) -> Self {
202        AttributeText::quoted(pos.dot_string())
203    }
204}
205
206impl<'a> From<ImageScale> for AttributeText<'a> {
207    fn from(scale: ImageScale) -> Self {
208        AttributeText::quoted(scale.dot_string())
209    }
210}
211
212impl<'a> From<LabelJustification> for AttributeText<'a> {
213    fn from(label_justification: LabelJustification) -> Self {
214        AttributeText::attr(label_justification.dot_string())
215    }
216}
217
218impl<'a> From<LabelLocation> for AttributeText<'a> {
219    fn from(label_location: LabelLocation) -> Self {
220        AttributeText::attr(label_location.dot_string())
221    }
222}
223
224impl<'a> From<NodeStyle> for AttributeText<'a> {
225    fn from(style: NodeStyle) -> Self {
226        AttributeText::attr(style.dot_string())
227    }
228}
229
230impl<'a> From<Ordering> for AttributeText<'a> {
231    fn from(ordering: Ordering) -> Self {
232        AttributeText::quoted(ordering.dot_string())
233    }
234}
235
236impl<'a> From<OutputMode> for AttributeText<'a> {
237    fn from(mode: OutputMode) -> Self {
238        AttributeText::quoted(mode.dot_string())
239    }
240}
241
242impl<'a> From<PackMode> for AttributeText<'a> {
243    fn from(mode: PackMode) -> Self {
244        AttributeText::quoted(mode.dot_string())
245    }
246}
247
248impl<'a> From<PageDirection> for AttributeText<'a> {
249    fn from(page_direction: PageDirection) -> Self {
250        AttributeText::attr(page_direction.dot_string())
251    }
252}
253
254impl<'a> From<Point> for AttributeText<'a> {
255    fn from(point: Point) -> Self {
256        AttributeText::quoted(point.dot_string())
257    }
258}
259
260impl<'a> From<PortPosition> for AttributeText<'a> {
261    fn from(port_position: PortPosition) -> Self {
262        AttributeText::quoted(port_position.dot_string())
263    }
264}
265
266impl<'a> From<RankDir> for AttributeText<'a> {
267    fn from(rank_dir: RankDir) -> Self {
268        AttributeText::attr(rank_dir.dot_string())
269    }
270}
271
272impl<'a> From<Ratio> for AttributeText<'a> {
273    fn from(ratio: Ratio) -> Self {
274        match ratio {
275            Ratio::Aspect(_aspect) => AttributeText::attr(ratio.dot_string()),
276            _ => AttributeText::quoted(ratio.dot_string()),
277        }
278    }
279}
280
281impl<'a> From<Rectangle> for AttributeText<'a> {
282    fn from(rectangle: Rectangle) -> Self {
283        AttributeText::quoted(rectangle.dot_string())
284    }
285}
286
287impl<'a> From<Shape> for AttributeText<'a> {
288    fn from(shape: Shape) -> Self {
289        AttributeText::attr(shape.dot_string())
290    }
291}
292
293impl<'a> From<Splines> for AttributeText<'a> {
294    fn from(splines: Splines) -> Self {
295        AttributeText::quoted(splines.dot_string())
296    }
297}
298
299impl<'a> From<SplineType> for AttributeText<'a> {
300    fn from(spline_type: SplineType) -> Self {
301        AttributeText::quoted(spline_type.dot_string())
302    }
303}
304
305impl<'a> From<Styles> for AttributeText<'a> {
306    fn from(styles: Styles) -> Self {
307        match styles {
308            Styles::Edge(s) => AttributeText::from(s),
309            Styles::Node(s) => AttributeText::from(s),
310            Styles::Graph(s) => AttributeText::from(s),
311        }
312    }
313}
314
315impl<'a> From<ViewPort> for AttributeText<'a> {
316    fn from(viewport: ViewPort) -> Self {
317        AttributeText::quoted(viewport.dot_string())
318    }
319}
320
321
322impl<'a> From<u32> for AttributeText<'a> {
323    fn from(v: u32) -> Self {
324        AttributeText::attr(v.to_string())
325    }
326}
327
328impl<'a> From<String> for AttributeText<'a> {
329    fn from(string: String) -> Self {
330        // FIXME Attempt to select the enum type appropriate for the encoding required?
331        if is_alphanum(&string) {
332            AttributeText::attr(string)
333        } else {
334            AttributeText::quoted(string)
335        }
336    }
337}
338
339impl<'a> From<&str> for AttributeText<'a> {
340    fn from(string: &str) -> Self {
341        // FIXME Attempt to select the enum type appropriate for the encoding required?
342        if is_alphanum(&String::from(string)) {
343            AttributeText::attr(String::from(string))
344        } else {
345            AttributeText::quoted(String::from(string))
346        }
347    }
348}
349
350impl<'a> From<AttributeText<'a>> for String {
351    fn from(attribute_text: AttributeText) -> Self {
352        attribute_text.dot_string()
353    }
354}
355
356#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, Debug, Clone)]
357pub enum AttributeType {
358    Graph,
359    Node,
360    Edge,
361}
362
363pub trait GraphAttributes<'a> {
364    fn background(&mut self, background: String) -> &mut Self {
365        self.add_attribute("_background", AttributeText::attr(background))
366    }
367
368    /// The color used as the background for entire canvas.
369    fn background_color(&mut self, background_color: Color<'a>) -> &mut Self {
370        self.add_attribute("bgcolor", AttributeText::from(background_color))
371    }
372
373    // TODO: constrain
374    /// The color used as the background for entire canvas with a gradient fill.
375    /// A colon-separated list of weighted color values: WC(:WC)* where each WC has the form C(;F)?
376    /// with C a color value and the optional F a floating-point number, 0 ≤ F ≤ 1.
377    /// The sum of the floating-point numbers in a colorList must sum to at most 1.
378    fn background_colorlist(&mut self, background_colors: ColorList<'a>) -> &mut Self {
379        self.add_attribute("bgcolor", AttributeText::from(background_colors))
380    }
381
382    /// Type: rect which is "%f,%f,%f,%f"
383    /// The rectangle llx,lly,urx,ury gives the coordinates, in points, of the lower-left corner (llx,lly)
384    /// and the upper-right corner (urx,ury).
385    fn bounding_box(&mut self, bounding_box: String) -> &mut Self {
386        self.add_attribute("bb", AttributeText::quoted(bounding_box))
387    }
388
389    /// If true, the drawing is centered in the output canvas.
390    fn center(&mut self, center: bool) -> &mut Self {
391        self.add_attribute("center", AttributeText::from(center))
392    }
393
394    /// Specifies the character encoding used when interpreting string input as a text label.
395    fn charset(&mut self, charset: String) -> &mut Self {
396        self.add_attribute("charset", AttributeText::quoted(charset))
397    }
398
399    /// Classnames to attach to the node, edge, graph, or cluster’s SVG element.
400    /// Combine with stylesheet for styling SVG output using CSS classnames.
401    /// Multiple space-separated classes are supported.
402    fn class(&mut self, class: String) -> &mut Self {
403        Attributes::class(self.get_attributes_mut(), class);
404        self
405    }
406
407    /// Mode used for handling clusters.
408    /// If clusterrank=local, a subgraph whose name begins with cluster is given special treatment.
409    /// The subgraph is laid out separately, and then integrated as a unit into its parent graph,
410    ///  with a bounding rectangle drawn about it.
411    /// If the cluster has a label parameter, this label is displayed within the rectangle.
412    /// Note also that there can be clusters within clusters.
413    /// The modes clusterrank=global and clusterrank=none appear to be identical, both turning off the special cluster processing.
414    fn cluster_rank(&mut self, cluster_rank: ClusterMode) -> &mut Self {
415        self.add_attribute("clusterrank", AttributeText::from(cluster_rank))
416    }
417
418    /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
419    /// If any fraction is used, the colors are drawn in series, with each color being given
420    /// roughly its specified fraction of the edge.
421    fn color(&mut self, color: Color<'a>) -> &mut Self {
422        Attributes::color(self.get_attributes_mut(), color);
423        self
424    }
425
426    /// This attribute specifies a color scheme namespace: the context for interpreting color names.
427    /// In particular, if a color value has form "xxx" or "//xxx", then the color xxx will be evaluated
428    /// according to the current color scheme. If no color scheme is set, the standard X11 naming is used.
429    /// For example, if colorscheme=bugn9, then color=7 is interpreted as color="/bugn9/7".
430    fn color_scheme(&mut self, color_scheme: String) -> &mut Self {
431        Attributes::color_scheme(self.get_attributes_mut(), color_scheme);
432        self
433    }
434
435    /// Comments are inserted into output. Device-dependent
436    fn comment(&mut self, comment: String) -> &mut Self {
437        Attributes::comment(self.get_attributes_mut(), comment);
438        self
439    }
440
441    fn compound(&mut self, compound: String) -> &mut Self {
442        self.add_attribute("compound", AttributeText::quoted(compound))
443    }
444
445    fn concentrate(&mut self, concentrate: String) -> &mut Self {
446        self.add_attribute("concentrate", AttributeText::quoted(concentrate))
447    }
448
449    /// Specifies the expected number of pixels per inch on a display device.
450    /// Also known as resolution
451    fn dpi(&mut self, dpi: f32) -> &mut Self {
452        self.add_attribute("dpi", AttributeText::from(dpi))
453    }
454
455    /// Color used to fill the background of a node or cluster assuming style=filled, or a filled arrowhead.
456    fn fill_color(&mut self, fill_color: Color<'a>) -> &mut Self {
457        Attributes::fill_color(self.get_attributes_mut(), fill_color);
458        self
459    }
460
461    /// Color used to fill the background, with a gradient, of a node or cluster assuming
462    /// style=filled, or a filled arrowhead.
463    fn fill_color_with_colorlist(&mut self, fill_colors: ColorList<'a>) -> &mut Self {
464        Attributes::fill_color_with_colorlist(self.get_attributes_mut(), fill_colors);
465        self
466    }
467
468    /// Color used to fill the background, with a gradient, of a node or cluster assuming
469    /// style=filled, or a filled arrowhead.
470    /// TODO: example
471    /// [crate::attributes::GraphAttributes::dpi]
472    fn fill_color_with_iter<I>(&mut self, fill_colors: I) -> &mut Self
473    where
474        I: IntoIterator,
475        I::Item: IntoWeightedColor<'a>,
476    {
477        Attributes::fill_color_with_iter(self.get_attributes_mut(), fill_colors);
478        self
479    }
480
481    /// Color used for text.
482    fn font_color(&mut self, font_color: Color<'a>) -> &mut Self {
483        Attributes::font_color(self.get_attributes_mut(), font_color);
484        self
485    }
486
487    /// Font used for text.
488    fn font_name(&mut self, font_name: String) -> &mut Self {
489        Attributes::font_name(self.get_attributes_mut(), font_name);
490        self
491    }
492
493    fn font_names(&mut self, font_names: String) -> &mut Self {
494        self.add_attribute("fontnames", AttributeText::quoted(font_names))
495    }
496
497    fn font_path(&mut self, font_path: String) -> &mut Self {
498        self.add_attribute("fontpath", AttributeText::quoted(font_path))
499    }
500
501    /// Font size, in points, used for text.
502    /// default: 14.0, minimum: 1.0
503    fn font_size(&mut self, font_size: f32) -> &mut Self {
504        if font_size < 1.0 {
505            self.add_validation_error("fontsize", "Must be greater than or equal to 1.0")
506        }
507        Attributes::font_size(self.get_attributes_mut(), font_size);
508        self
509    }
510
511    fn force_label(&mut self, force_label: bool) -> &mut Self {
512        self.add_attribute("forcelabel", AttributeText::from(force_label))
513    }
514
515    /// If a gradient fill is being used, this determines the angle of the fill.
516    fn gradient_angle(&mut self, gradient_angle: u32) -> &mut Self {
517        Attributes::gradient_angle(self.get_attributes_mut(), gradient_angle);
518        self
519    }
520
521    fn image_path(&mut self, image_path: String) -> &mut Self {
522        self.add_attribute("imagepath", AttributeText::escaped(image_path))
523    }
524
525    /// An escString or an HTML label.
526    fn label<S: Into<String>>(&mut self, label: S) -> &mut Self {
527        Attributes::label(self.get_attributes_mut(), label);
528        self
529    }
530
531    /// If labeljust=r, the label is right-justified within bounding rectangle
532    /// If labeljust=l, left-justified
533    /// Else the label is centered.
534    fn label_justification(
535        &mut self,
536        label_justification: LabelJustification,
537    ) -> &mut Self {
538        self.add_attribute("labeljust", AttributeText::from(label_justification))
539    }
540
541    // Vertical placement of labels for nodes, root graphs and clusters.
542    // For graphs and clusters, only labelloc=t and labelloc=b are allowed, corresponding to
543    // placement at the top and bottom, respectively.
544    // By default, root graph labels go on the bottom and cluster labels go on the top.
545    // Note that a subgraph inherits attributes from its parent. Thus, if the root graph sets
546    // labelloc=b, the subgraph inherits this value.
547    // For nodes, this attribute is used only when the height of the node is larger than the height
548    // of its label.
549    // If labelloc=t, labelloc=c, labelloc=b, the label is aligned with the top, centered, or
550    // aligned with the bottom of the node, respectively.
551    // By default, the label is vertically centered.
552    fn label_location(&mut self, label_location: LabelLocation) -> &mut Self {
553        Attributes::label_location(self.get_attributes_mut(), label_location);
554        self
555    }
556
557    fn landscape(&mut self, landscape: bool) -> &mut Self {
558        self.add_attribute("landscape", AttributeText::from(landscape))
559    }
560
561    /// Specifies the separator characters used to split an attribute of type layerRange into a
562    /// list of ranges.
563    fn layer_list_sep(&mut self, layer_list_sep: String) -> &mut Self {
564        self.add_attribute("layerlistsep", AttributeText::attr(layer_list_sep))
565    }
566
567    /// Specifies a linearly ordered list of layer names attached to the graph
568    /// The graph is then output in separate layers.
569    /// Only those components belonging to the current output layer appear.
570    fn layers(&mut self, layers: String) -> &mut Self {
571        Attributes::layer(self.get_attributes_mut(), layers);
572        self
573    }
574
575    /// Selects a list of layers to be emitted.
576    fn layer_select(&mut self, layer_select: String) -> &mut Self {
577        self.add_attribute("layerselect", AttributeText::attr(layer_select))
578    }
579
580    /// Specifies the separator characters used to split the layers attribute into a list of layer names.
581    /// default: ":\t "
582    fn layer_sep(&mut self, layer_sep: String) -> &mut Self {
583        self.add_attribute("layersep", AttributeText::attr(layer_sep))
584    }
585
586    /// Height of graph or cluster label, in inches.
587    fn lheight(&mut self, lheight: f32) -> &mut Self {
588        self.add_attribute("lheight", AttributeText::from(lheight))
589    }
590
591    /// Label position
592    /// The position indicates the center of the label.
593    fn label_position(&mut self, lp: Point) -> &mut Self {
594        Attributes::label_position(self.get_attributes_mut(), lp);
595        self
596    }
597
598    /// Width of graph or cluster label, in inches.
599    fn lwidth(&mut self, lwidth: f32) -> &mut Self {
600        self.add_attribute("lwidth", AttributeText::from(lwidth))
601    }
602
603    /// Sets x and y margins of canvas, in inches.
604    /// Both margins are set equal to the given value.
605    /// See [`crate::attributes::GraphAttributes::margin_point`]
606    fn margin(&mut self, margin: f32) -> &mut Self {
607        self.margin_point(Point::new_2d(margin, margin))
608    }
609
610    /// Sets x and y margins of canvas, in inches.
611    /// Note that the margin is not part of the drawing but just empty space left around the drawing.
612    /// The margin basically corresponds to a translation of drawing, as would be necessary to
613    /// center a drawing on a page. Nothing is actually drawn in the margin.
614    /// To actually extend the background of a drawing, see the pad attribute.
615    /// Whilst it is possible to create a Point value with either a third co-ordinate
616    /// or a forced position, these are ignored for printing.
617    /// By default, the value is 0.11,0.055.
618    fn margin_point(&mut self, margin: Point) -> &mut Self {
619        Attributes::margin(self.get_attributes_mut(), margin);
620        self
621    }
622
623    /// Multiplicative scale factor used to alter the MinQuit (default = 8) and
624    /// MaxIter (default = 24) parameters used during crossing minimization.
625    /// These correspond to the number of tries without improvement before quitting and the
626    /// maximum number of iterations in each pass.
627    fn mclimit(&mut self, mclimit: f32) -> &mut Self {
628        self.add_attribute("mclimit", AttributeText::from(mclimit))
629    }
630
631    /// Specifies the minimum separation between all nodes.
632    fn mindist(&mut self, mindist: u32) -> &mut Self {
633        self.add_attribute("mindist", AttributeText::from(mindist))
634    }
635
636    /// Whether to use a single global ranking, ignoring clusters.
637    /// The original ranking algorithm in dot is recursive on clusters.
638    /// This can produce fewer ranks and a more compact layout, but sometimes at the cost of a
639    /// head node being place on a higher rank than the tail node.
640    /// It also assumes that a node is not constrained in separate, incompatible subgraphs.
641    /// For example, a node cannot be in a cluster and also be constrained by rank=same with
642    /// a node not in the cluster.
643    /// This allows nodes to be subject to multiple constraints.
644    /// Rank constraints will usually take precedence over edge constraints.
645    fn newrank(&mut self, newrank: bool) -> &mut Self {
646        self.add_attribute("newrank", AttributeText::from(newrank))
647    }
648
649    /// specifies the minimum space between two adjacent nodes in the same rank, in inches.
650    /// default: 0.25, minimum: 0.02
651    fn nodesep(&mut self, nodesep: f32) -> &mut Self {
652        if nodesep < 0.02 {
653            self.add_validation_error("nodesep", "Must be greater than or equal to 0.02")
654        }
655        self.add_attribute("nodesep", AttributeText::from(nodesep))
656    }
657
658    /// By default, the justification of multi-line labels is done within the largest context that makes sense.
659    /// Thus, in the label of a polygonal node, a left-justified line will align with the left side
660    /// of the node (shifted by the prescribed margin).
661    /// In record nodes, left-justified line will line up with the left side of the enclosing column
662    /// of fields.
663    /// If nojustify=true, multi-line labels will be justified in the context of itself.
664    /// For example, if nojustify is set, the first label line is long, and the second is shorter
665    /// and left-justified,
666    /// the second will align with the left-most character in the first line, regardless of how
667    /// large the node might be.
668    fn no_justify(&mut self, no_justify: bool) -> &mut Self {
669        Attributes::no_justify(self.get_attributes_mut(), no_justify);
670        self
671    }
672
673    /// Sets number of iterations in network simplex applications.
674    /// nslimit is used in computing node x coordinates.
675    /// If defined, # iterations = nslimit * # nodes; otherwise, # iterations = MAXINT.
676    fn nslimit(&mut self, nslimit: f32) -> &mut Self {
677        self.add_attribute("nslimit", AttributeText::from(nslimit))
678    }
679
680    /// If ordering="out", then the outedges of a node, that is, edges with the node as its tail
681    /// node, must appear left-to-right in the same order in which they are defined in the input.
682    ///
683    /// If ordering="in", then the inedges of a node must appear left-to-right in the same order in
684    /// which they are defined in the input.
685    ///
686    /// If defined as a graph or subgraph attribute, the value is applied to all nodes in the graph
687    /// or subgraph.
688    ///
689    /// Note that the graph attribute takes precedence over the node attribute.
690    fn ordering(&mut self, ordering: Ordering) -> &mut Self {
691        Attributes::ordering(self.get_attributes_mut(), ordering);
692        self
693    }
694
695    // TODO: constrain to 0 - 360. Docs say min is 360 which should be max right?
696    /// Used only if rotate is not defined.
697    /// Default: 0.0 and minimum: 360.0
698    fn orientation(&mut self, orientation: f32) -> &mut Self {
699        if orientation < 0.0 || orientation > 360.0 {
700            self.add_validation_error("orientation", "Must be between 0 and 360")
701        }
702        Attributes::orientation(self.get_attributes_mut(), orientation);
703        self
704    }
705
706    /// Specify order in which nodes and edges are drawn.
707    /// default: breadthfirst
708    fn output_order(&mut self, output_order: OutputMode) -> &mut Self {
709        self.add_attribute("outputorder", AttributeText::from(output_order))
710    }
711
712    /// Whether each connected component of the graph should be laid out separately, and then the
713    /// graphs packed together.
714    /// If false, the entire graph is laid out together.
715    /// The granularity and method of packing is influenced by the packmode attribute.
716    fn pack(&mut self, pack: bool) -> &mut Self {
717        self.add_attribute("pack", AttributeText::from(pack))
718    }
719
720    /// Whether each connected component of the graph should be laid out separately, and then
721    /// the graphs packed together.
722    /// This is used as the size, in points,of a margin around each part; otherwise, a default
723    /// margin of 8 is used.
724    /// pack is treated as true if the value of pack iso a non-negative integer.
725    fn pack_int(&mut self, pack: u32) -> &mut Self {
726        self.add_attribute("pack", AttributeText::from(pack))
727    }
728
729    /// This indicates how connected components should be packed (cf. packMode).
730    /// Note that defining packmode will automatically turn on packing as though one had set pack=true.
731    fn pack_mode(&mut self, pack_mode: PackMode) -> &mut Self {
732        self.add_attribute("packmode", AttributeText::from(pack_mode))
733    }
734
735    /// Specifies how much, in inches, to extend the drawing area around the minimal area needed
736    /// to draw the graph.
737    /// Both the x and y pad values are set equal to the given value.
738    /// See [`crate::attributes::GraphAttributes::pad_point`]
739    fn pad(&mut self, pad: f32) -> &mut Self {
740        self.pad_point(Point::new_2d(pad, pad))
741    }
742
743    /// Specifies how much, in inches, to extend the drawing area around the minimal area needed to
744    /// draw the graph.
745    /// This area is part of the drawing and will be filled with the background color, if appropriate.
746    /// default: 0.0555
747    fn pad_point(&mut self, pad: Point) -> &mut Self {
748        self.add_attribute("pad", AttributeText::from(pad))
749    }
750
751    /// Width and height of output pages, in inches.
752    /// Value given is used for both the width and height.
753    fn page(&mut self, page: f32) -> &mut Self {
754        self.add_attribute("page", AttributeText::from(page))
755    }
756
757    /// Width and height of output pages, in inches.
758    fn page_point(&mut self, page: Point) -> &mut Self {
759        self.add_attribute("page", AttributeText::from(page))
760    }
761
762    /// The order in which pages are emitted.
763    /// Used only if page is set and applicable.
764    /// Limited to one of the 8 row or column major orders.
765    fn page_dir(&mut self, page_dir: PageDirection) -> &mut Self {
766        self.add_attribute("pagedir", AttributeText::from(page_dir))
767    }
768
769    /// If quantum > 0.0, node label dimensions will be rounded to integral multiples of the quantum.
770    /// default: 0.0, minimum: 0.0
771    fn quantum(&mut self, quantum: f32) -> &mut Self {
772        if quantum < 0.0 {
773            self.add_validation_error("quantum", "Must be greater than or equal to 0")
774        }
775        self.add_attribute("quantum", AttributeText::from(quantum))
776    }
777
778    /// Sets direction of graph layout.
779    /// For example, if rankdir="LR", and barring cycles, an edge T -> H; will go from left to right.
780    /// By default, graphs are laid out from top to bottom.
781    /// This attribute also has a side-effect in determining how record nodes are interpreted.
782    /// See record shapes.
783    fn rank_dir(&mut self, rank_dir: RankDir) -> &mut Self {
784        self.add_attribute("rankdir", AttributeText::from(rank_dir))
785    }
786
787    /// sets the desired rank separation, in inches.
788    /// This is the minimum vertical distance between the bottom of the nodes in one rank
789    /// and the tops of nodes in the next. If the value contains equally,
790    /// the centers of all ranks are spaced equally apart.
791    /// Note that both settings are possible, e.g., ranksep="1.2 equally".
792    fn rank_sep(&mut self, rank_sep: String) -> &mut Self {
793        self.add_attribute("ranksep", AttributeText::attr(rank_sep))
794    }
795
796    /// Sets the aspect ratio (drawing height/drawing width) for the drawing.
797    /// Note that this is adjusted before the size attribute constraints are enforced.
798    fn ratio(&mut self, ratio: Ratio) -> &mut Self {
799        self.add_attribute("ratio", AttributeText::from(ratio))
800    }
801
802    /// If true and there are multiple clusters, run crossing minimization a second time.
803    fn remincross(&mut self, remincross: bool) -> &mut Self {
804        self.add_attribute("remincross", AttributeText::from(remincross))
805    }
806
807    /// If rotate=90, sets drawing orientation to landscape.
808    fn rotate(&mut self, rotate: u32) -> &mut Self {
809        self.add_attribute("rotate", AttributeText::from(rotate))
810    }
811
812    /// Print guide boxes in PostScript at the beginning of routesplines if showboxes=1, or at
813    /// the end if showboxes=2.
814    /// (Debugging, TB mode only!)
815    /// default: 0, minimum: 0
816    fn show_boxes(&mut self, show_boxes: u32) -> &mut Self {
817        Attributes::show_boxes(self.get_attributes_mut(), show_boxes);
818        self
819    }
820
821    /// Maximum width and height of drawing, in inches.
822    /// Value used for both the width and the height.
823    /// If defined and the drawing is larger than the given size, the drawing
824    /// is uniformly scaled down so that it fits within the given size.
825    /// If desired_min is true, and both both dimensions of the drawing
826    /// are less than size, the drawing is scaled up uniformly until at
827    /// least one dimension equals its dimension in size.
828    /// See [`crate::attributes::GraphAttributes::size_point`]
829    fn size(&mut self, size: u32, desired_min: bool) -> &mut Self {
830        self.size_point(Point {
831            x: size as f32,
832            y: size as f32,
833            z: None,
834            force_pos: desired_min,
835        })
836    }
837
838    /// Maximum width and height of drawing, in inches.
839    /// If defined and the drawing is larger than the given size, the drawing
840    /// is uniformly scaled down so that it fits within the given size.
841    /// If desired_min is true, and both both dimensions of the drawing
842    /// are less than size, the drawing is scaled up uniformly until at
843    /// least one dimension equals its dimension in size.
844    fn size_point(&mut self, size: Point) -> &mut Self {
845        self.add_attribute("size", AttributeText::from(size))
846    }
847
848    /// If packmode indicates an array packing, sortv specifies an insertion order
849    /// among the components, with smaller values inserted first.
850    /// default: 0, minimum: 0
851    fn sortv(&mut self, sortv: u32) -> &mut Self {
852        Attributes::sortv(self.get_attributes_mut(), sortv);
853        self
854    }
855
856    /// Controls how, and if, edges are represented.
857    fn splines(&mut self, splines: Splines) -> &mut Self {
858        self.add_attribute("splines", AttributeText::from(splines))
859    }
860
861    /// Set style information for components of the graph.
862    fn style(&mut self, style: GraphStyle) -> &mut Self {
863        Attributes::style(self.get_attributes_mut(), Styles::Graph(style));
864        self
865    }
866
867    /// A URL or pathname specifying an XML style sheet, used in SVG output.
868    /// Combine with class to style elements using CSS selectors.
869    fn stylesheet(&mut self, stylesheet: String) -> &mut Self {
870        self.add_attribute("stylesheet", AttributeText::attr(stylesheet))
871    }
872
873    /// If the object has a URL, this attribute determines which window of the browser is used for the URL.
874    fn target(&mut self, target: String) -> &mut Self {
875        Attributes::target(self.get_attributes_mut(), target);
876        self
877    }
878
879    /// Whether internal bitmap rendering relies on a truecolor color model or uses a color palette.
880    /// If truecolor is unset, truecolor is not used unless there is a shapefile property
881    /// for some node in the graph.
882    /// The output model will use the input model when possible.
883    fn true_color(&mut self, true_color: bool) -> &mut Self {
884        self.add_attribute("truecolor", AttributeText::from(true_color))
885    }
886
887    /// Hyperlinks incorporated into device-dependent output.
888    fn url(&mut self, url: String) -> &mut Self {
889        Attributes::url(self.get_attributes_mut(), url);
890        self
891    }
892
893    /// Clipping window on final drawing.
894    /// viewport supersedes any size attribute.
895    /// The width and height of the viewport specify precisely the final size of the output.
896    /// The viewPort W,H,Z,x,y or W,H,Z,N specifies a viewport for the final image.
897    /// The pair (W,H) gives the dimensions (width and height) of the final image, in points.
898    /// The optional Z is the zoom factor, i.e., the image in the original layout will be
899    /// W/Z by H/Z points in size. By default, Z is 1.
900    /// The optional last part is either a pair (x,y) giving a position in the original layout
901    /// of the graph,
902    /// in points, of the center of the viewport, or the name N of a node whose center should used
903    /// as the focus.
904    fn viewport(&mut self, viewport: ViewPort) -> &mut Self {
905        self.add_attribute("viewport", AttributeText::from(viewport))
906    }
907
908    /// Add an attribute to the node.
909    fn add_attribute<S: Into<String>>(
910        &mut self,
911        key: S,
912        value: AttributeText<'a>,
913    ) -> &mut Self;
914
915    /// Add multiple attributes to the node.
916    fn add_attributes(
917        &'a mut self,
918        attributes: HashMap<String, AttributeText<'a>>,
919    ) -> &mut Self;
920
921    fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>>;
922
923    fn add_validation_error(&mut self, field: &'static str, message: &'static str);
924}
925
926impl<'a> GraphAttributes<'a> for GraphAttributeStatementBuilder<'a> {
927    fn add_attribute<S: Into<String>>(
928        &mut self,
929        key: S,
930        value: AttributeText<'a>,
931    ) -> &mut Self {
932        self.attributes.insert(key.into(), value);
933        self
934    }
935
936    /// Add multiple attributes to the node.
937    fn add_attributes(
938        &'a mut self,
939        attributes: HashMap<String, AttributeText<'a>>,
940    ) -> &mut Self {
941        self.attributes.extend(attributes);
942        self
943    }
944
945    fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>> {
946        &mut self.attributes
947    }
948
949    fn add_validation_error(&mut self, field: &'static str, message: &'static str) {
950        self.errors.push(ValidationError {
951            field: Borrowed(field),
952            message: Borrowed(message),
953        })
954    }
955}
956
957// I'm not a huge fan of needing this builder but having a hard time getting around &mut without it
958pub struct GraphAttributeStatementBuilder<'a> {
959    pub attributes: IndexMap<String, AttributeText<'a>>,
960    errors: Vec<ValidationError>,
961}
962
963impl<'a> GraphAttributeStatementBuilder<'a> {
964    pub fn new() -> Self {
965        Self {
966            attributes: IndexMap::new(),
967            errors: Vec::new(),
968        }
969    }
970
971    pub fn build(&self) -> ValidationResult<IndexMap<String, AttributeText<'a>>> {
972        if !self.errors.is_empty() {
973            return Err(self.errors.clone());
974        }
975        Ok(self.build_ignore_validation())
976    }
977
978    pub fn build_ignore_validation(&self) -> IndexMap<String, AttributeText<'a>> {
979        self.attributes.clone()
980    }
981}
982
983pub(crate) struct Attributes;
984impl Attributes {
985    pub fn class(attributes: &mut IndexMap<String, AttributeText>, class: String) {
986        Self::add_attribute(attributes, "class", AttributeText::quoted(class))
987    }
988
989    pub fn color<'a>(
990        attributes: &mut IndexMap<String, AttributeText<'a>>,
991        color: Color<'a>,
992    ) {
993        Self::add_attribute(attributes, "color", AttributeText::from(color))
994    }
995
996    pub fn color_with_colorlist<'a>(
997        attributes: &mut IndexMap<String, AttributeText<'a>>,
998        color: ColorList<'a>,
999    ) {
1000        Self::add_attribute(attributes, "color", AttributeText::from(color))
1001    }
1002
1003    pub fn color_scheme(
1004        attributes: &mut IndexMap<String, AttributeText>,
1005        color_scheme: String,
1006    ) {
1007        Self::add_attribute(
1008            attributes,
1009            "colorscheme",
1010            AttributeText::quoted(color_scheme),
1011        )
1012    }
1013
1014    pub fn comment(attributes: &mut IndexMap<String, AttributeText>, comment: String) {
1015        Self::add_attribute(attributes, "comment", AttributeText::quoted(comment))
1016    }
1017
1018    pub fn fill_color<'a>(
1019        attributes: &mut IndexMap<String, AttributeText<'a>>,
1020        fill_color: Color<'a>,
1021    ) {
1022        Self::add_attribute(attributes, "fillcolor", AttributeText::from(fill_color))
1023    }
1024
1025    pub fn fill_color_with_colorlist<'a>(
1026        attributes: &mut IndexMap<String, AttributeText<'a>>,
1027        fill_colors: ColorList<'a>,
1028    ) {
1029        Self::add_attribute(attributes, "fillcolor", AttributeText::from(fill_colors))
1030    }
1031
1032    pub fn fill_color_with_iter<'a, I>(
1033        attributes: &mut IndexMap<String, AttributeText<'a>>,
1034        fill_colors: I,
1035    ) where
1036        I: IntoIterator,
1037        I::Item: IntoWeightedColor<'a>,
1038    {
1039        let colors: Vec<WeightedColor> = fill_colors
1040            .into_iter()
1041            .map(|e| e.into_weighted_color())
1042            .collect();
1043
1044        let color_list = ColorList { colors };
1045
1046        Self::add_attribute(attributes, "fillcolor", AttributeText::from(color_list))
1047    }
1048
1049    pub fn font_color<'a>(
1050        attributes: &mut IndexMap<String, AttributeText<'a>>,
1051        font_color: Color<'a>,
1052    ) {
1053        Self::add_attribute(attributes, "fontcolor", AttributeText::from(font_color))
1054    }
1055
1056    pub fn font_name(
1057        attributes: &mut IndexMap<String, AttributeText>,
1058        font_name: String,
1059    ) {
1060        Self::add_attribute(attributes, "fontname", AttributeText::quoted(font_name))
1061    }
1062
1063    pub fn font_size(attributes: &mut IndexMap<String, AttributeText>, font_size: f32) {
1064        Self::add_attribute(attributes, "fontsize", AttributeText::from(font_size))
1065    }
1066
1067    pub fn gradient_angle(
1068        attributes: &mut IndexMap<String, AttributeText>,
1069        gradient_angle: u32,
1070    ) {
1071        Self::add_attribute(
1072            attributes,
1073            "gradientangle",
1074            AttributeText::from(gradient_angle),
1075        )
1076    }
1077
1078    pub fn label<S: Into<String>>(attributes: &mut IndexMap<String, AttributeText>, text: S) {
1079        Self::add_attribute(attributes, "label", AttributeText::quoted(text.into()));
1080    }
1081
1082    pub fn label_location(
1083        attributes: &mut IndexMap<String, AttributeText>,
1084        label_location: LabelLocation,
1085    ) {
1086        Self::add_attribute(attributes, "labelloc", AttributeText::from(label_location))
1087    }
1088
1089    // TODO: Layer, LayerRange, LayerList, LayerSep
1090    // TODO: layer struct
1091    pub fn layer(attributes: &mut IndexMap<String, AttributeText>, layer: String) {
1092        Self::add_attribute(attributes, "layer", AttributeText::attr(layer))
1093    }
1094
1095    pub fn label_position(attributes: &mut IndexMap<String, AttributeText>, lp: Point) {
1096        Self::add_attribute(attributes, "lp", AttributeText::from(lp))
1097    }
1098
1099    pub fn margin(attributes: &mut IndexMap<String, AttributeText>, margin: Point) {
1100        Self::add_attribute(attributes, "margin", AttributeText::from(margin))
1101    }
1102
1103    pub fn no_justify(
1104        attributes: &mut IndexMap<String, AttributeText>,
1105        no_justify: bool,
1106    ) {
1107        Self::add_attribute(attributes, "nojustify", AttributeText::from(no_justify))
1108    }
1109
1110    pub fn ordering(
1111        attributes: &mut IndexMap<String, AttributeText>,
1112        ordering: Ordering,
1113    ) {
1114        Self::add_attribute(attributes, "ordering", AttributeText::from(ordering))
1115    }
1116
1117    pub fn orientation(
1118        attributes: &mut IndexMap<String, AttributeText>,
1119        orientation: f32,
1120    ) {
1121        Self::add_attribute(attributes, "orientation", AttributeText::from(orientation))
1122    }
1123
1124    pub fn pen_width(attributes: &mut IndexMap<String, AttributeText>, pen_width: f32) {
1125        Self::add_attribute(attributes, "penwidth", AttributeText::from(pen_width))
1126    }
1127
1128    // TODO: splinetype
1129    pub fn pos(attributes: &mut IndexMap<String, AttributeText>, pos: Point) {
1130        Self::add_attribute(attributes, "pos", AttributeText::from(pos))
1131    }
1132
1133    pub fn show_boxes(
1134        attributes: &mut IndexMap<String, AttributeText>,
1135        show_boxes: u32,
1136    ) {
1137        Self::add_attribute(attributes, "showboxes", AttributeText::from(show_boxes))
1138    }
1139
1140    pub fn sortv(attributes: &mut IndexMap<String, AttributeText>, sortv: u32) {
1141        Self::add_attribute(attributes, "sortv", AttributeText::from(sortv))
1142    }
1143
1144    pub fn style(attributes: &mut IndexMap<String, AttributeText>, style: Styles) {
1145        Self::add_attribute(attributes, "style", AttributeText::from(style))
1146    }
1147
1148    pub fn target(attributes: &mut IndexMap<String, AttributeText>, target: String) {
1149        Self::add_attribute(attributes, "target", AttributeText::escaped(target))
1150    }
1151
1152    pub fn tooltip(attributes: &mut IndexMap<String, AttributeText>, tooltip: String) {
1153        Self::add_attribute(attributes, "tooltip", AttributeText::escaped(tooltip))
1154    }
1155
1156    pub fn url(attributes: &mut IndexMap<String, AttributeText>, url: String) {
1157        Self::add_attribute(attributes, "url", AttributeText::escaped(url))
1158    }
1159
1160    pub fn xlabel(attributes: &mut IndexMap<String, AttributeText>, width: String) {
1161        Self::add_attribute(attributes, "xlabel", AttributeText::escaped(width))
1162    }
1163
1164    pub fn xlp(attributes: &mut IndexMap<String, AttributeText>, xlp: Point) {
1165        Self::add_attribute(attributes, "xlp", AttributeText::from(xlp))
1166    }
1167
1168    pub fn add_attribute<'a, S: Into<String>>(
1169        attributes: &mut IndexMap<String, AttributeText<'a>>,
1170        key: S,
1171        value: AttributeText<'a>,
1172    ) {
1173        attributes.insert(key.into(), value);
1174    }
1175}
1176
1177pub trait NodeAttributes<'a> {
1178    /// Indicates the preferred area for a node or empty cluster when laid out by patchwork.
1179    /// default: 1.0, minimum: > 0
1180    fn area(&mut self, area: f32) -> &mut Self {
1181        if area <= 0.0 {
1182            self.add_validation_error("area", "Must be greater than 0")
1183        }
1184        self.add_attribute("area", AttributeText::from(area))
1185    }
1186
1187    /// Classnames to attach to the node’s SVG element.
1188    /// Combine with stylesheet for styling SVG output using CSS classnames.
1189    /// Multiple space-separated classes are supported.
1190    fn class(&mut self, class: String) -> &mut Self {
1191        Attributes::class(self.get_attributes_mut(), class);
1192        self
1193    }
1194
1195    /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
1196    fn color(&mut self, color: Color<'a>) -> &mut Self {
1197        Attributes::color(self.get_attributes_mut(), color);
1198        self
1199    }
1200
1201    /// This attribute specifies a color scheme namespace: the context for interpreting color names.
1202    /// In particular, if a color value has form "xxx" or "//xxx", then the color xxx will be evaluated
1203    /// according to the current color scheme. If no color scheme is set, the standard X11 naming is used.
1204    /// For example, if colorscheme=bugn9, then color=7 is interpreted as color="/bugn9/7".
1205    fn color_scheme(&mut self, color_scheme: String) -> &mut Self {
1206        Attributes::color_scheme(self.get_attributes_mut(), color_scheme);
1207        self
1208    }
1209
1210    /// Comments are inserted into output. Device-dependent
1211    fn comment(&mut self, comment: String) -> &mut Self {
1212        Attributes::comment(self.get_attributes_mut(), comment);
1213        self
1214    }
1215
1216    /// Distortion factor for shape=polygon.
1217    /// Positive values cause top part to be larger than bottom; negative values do the opposite.
1218    fn distortion(&mut self, distortion: f32) -> &mut Self {
1219        self.add_attribute("distortion", AttributeText::from(distortion))
1220    }
1221
1222    /// Color used to fill the background of a node or cluster assuming style=filled, or a filled arrowhead.
1223    fn fill_color(&mut self, fill_color: Color<'a>) -> &mut Self {
1224        Attributes::fill_color(self.get_attributes_mut(), fill_color);
1225        self
1226    }
1227
1228    /// Color used to fill the background, with a gradient, of a node or cluster assuming
1229    /// style=filled, or a filled arrowhead.
1230    fn fill_color_with_colorlist(&mut self, fill_colors: ColorList<'a>) -> &mut Self {
1231        Attributes::fill_color_with_colorlist(self.get_attributes_mut(), fill_colors);
1232        self
1233    }
1234
1235    /// Color used to fill the background, with a gradient, of a node or cluster assuming
1236    /// style=filled, or a filled arrowhead.
1237    /// TODO: example
1238    fn fill_color_with_iter<I>(&mut self, fill_colors: I) -> &mut Self
1239    where
1240        I: IntoIterator,
1241        I::Item: IntoWeightedColor<'a>,
1242    {
1243        Attributes::fill_color_with_iter(self.get_attributes_mut(), fill_colors);
1244        self
1245    }
1246
1247    /// If true, the node size is specified by the values of the width and height attributes only and
1248    /// is not expanded to contain the text label.
1249    /// There will be a warning if the label (with margin) cannot fit within these limits.
1250    /// If false, the size of a node is determined by smallest width and height needed
1251    /// to contain its label and image, if any, with a margin specified by the margin attribute.
1252    fn fixed_size(&mut self, fixed_size: bool) -> &mut Self {
1253        self.add_attribute("fixedsize", AttributeText::from(fixed_size))
1254    }
1255
1256    /// Color used for text.
1257    fn font_color(&mut self, font_color: Color<'a>) -> &mut Self {
1258        Attributes::font_color(self.get_attributes_mut(), font_color);
1259        self
1260    }
1261
1262    /// Font used for text.
1263    fn font_name(&mut self, font_name: String) -> &mut Self {
1264        Attributes::font_name(self.get_attributes_mut(), font_name);
1265        self
1266    }
1267
1268    /// Font size, in points, used for text.
1269    /// default: 14.0, minimum: 1.0
1270    fn font_size(&mut self, font_size: f32) -> &mut Self {
1271        Attributes::font_size(self.get_attributes_mut(), font_size);
1272        self
1273    }
1274
1275    /// If a gradient fill is being used, this determines the angle of the fill.
1276    fn gradient_angle(&mut self, gradient_angle: u32) -> &mut Self {
1277        Attributes::gradient_angle(self.get_attributes_mut(), gradient_angle);
1278        self
1279    }
1280
1281    /// If the end points of an edge belong to the same group, i.e., have the same group attribute,
1282    /// parameters are set to avoid crossings and keep the edges straight.
1283    fn group(&mut self, group: String) -> &mut Self {
1284        self.add_attribute("group", AttributeText::attr(group))
1285    }
1286
1287    /// Height of node, in inches.
1288    /// default: 0.5, minimum: 0.02
1289    fn height(&mut self, height: f32) -> &mut Self {
1290        if height < 0.02 {
1291            self.add_validation_error("height", "Must be greater than or equal to 0.02")
1292        }
1293        self.add_attribute("height", AttributeText::from(height))
1294    }
1295
1296    /// Gives the name of a file containing an image to be displayed inside a node.
1297    /// The image file must be in one of the recognized formats,
1298    /// typically JPEG, PNG, GIF, BMP, SVG, or Postscript, and be able to be converted
1299    /// into the desired output format.
1300    fn image(&mut self, image: String) -> &mut Self {
1301        self.add_attribute("image", AttributeText::quoted(image))
1302    }
1303
1304    /// Controls how an image is positioned within its containing node.
1305    /// Only has an effect when the image is smaller than the containing node.
1306    fn image_pos(&mut self, image_pos: ImagePosition) -> &mut Self {
1307        self.add_attribute("imagepos", AttributeText::from(image_pos))
1308    }
1309
1310    /// Controls how an image fills its containing node.
1311    fn image_scale_bool(&mut self, image_scale: bool) -> &mut Self {
1312        self.add_attribute("imagescale", AttributeText::from(image_scale))
1313    }
1314
1315    /// Controls how an image fills its containing node.
1316    fn image_scale(&mut self, image_scale: ImageScale) -> &mut Self {
1317        self.add_attribute("imagescale", AttributeText::from(image_scale))
1318    }
1319
1320    /// Text label attached to objects.
1321    fn label<S: Into<Cow<'a, str>>>(&mut self, text: S) -> &mut Self {
1322        self.add_attribute("label", AttributeText::quoted(text))
1323    }
1324
1325    // Vertical placement of labels for nodes, root graphs and clusters.
1326    // For graphs and clusters, only labelloc=t and labelloc=b are allowed,
1327    // corresponding to placement at the top and bottom, respectively.
1328    // By default, root graph labels go on the bottom and cluster labels go on the top.
1329    // Note that a subgraph inherits attributes from its parent.
1330    // Thus, if the root graph sets labelloc=b, the subgraph inherits this value.
1331    // For nodes, this attribute is used only when the height of the node is
1332    // larger than the height of its label.
1333    // If labelloc=t, labelloc=c, labelloc=b, the label is aligned with the top,
1334    // centered, or aligned with the bottom of the node, respectively.
1335    // By default, the label is vertically centered.
1336    fn label_location(&mut self, label_location: LabelLocation) -> &mut Self {
1337        Attributes::label_location(self.get_attributes_mut(), label_location);
1338        self
1339    }
1340
1341    /// Specifies layers in which the node, edge or cluster is present.
1342    fn layer(&mut self, layer: String) -> &mut Self {
1343        Attributes::layer(self.get_attributes_mut(), layer);
1344        self
1345    }
1346
1347    /// Sets x and y margins of canvas, in inches.
1348    /// Both margins are set equal to the given value.
1349    /// See [`crate::attributes::NodeAttributes::margin_point`]
1350    fn margin(&mut self, margin: f32) -> &mut Self {
1351        self.margin_point(Point::new_2d(margin, margin))
1352    }
1353
1354    /// Sets x and y margins of canvas, in inches.
1355    /// Specifies space left around the node’s label.
1356    /// Note that the margin is not part of the drawing but just empty space left around the drawing.
1357    /// The margin basically corresponds to a translation of drawing, as would be necessary to
1358    /// center a drawing on a page.
1359    /// Nothing is actually drawn in the margin.
1360    /// To actually extend the background of a drawing, see the pad attribute.
1361    /// Whilst it is possible to create a Point value with either a third co-ordinate
1362    /// or a forced position, these are ignored for printing.
1363    /// By default, the value is 0.11,0.055.
1364    fn margin_point(&mut self, margin: Point) -> &mut Self {
1365        Attributes::margin(self.get_attributes_mut(), margin);
1366        self
1367    }
1368
1369    /// By default, the justification of multi-line labels is done within the
1370    /// largest context that makes sense.
1371    /// Thus, in the label of a polygonal node, a left-justified line will align
1372    /// with the left side of the node (shifted by the prescribed margin).
1373    /// In record nodes, left-justified line will line up with the left side of
1374    /// the enclosing column of fields.
1375    /// If nojustify=true, multi-line labels will be justified in the context of itself.
1376    /// For example, if nojustify is set, the first label line is long, and the
1377    /// second is shorter and left-justified,
1378    /// the second will align with the left-most character in the first line,
1379    /// regardless of how large the node might be.
1380    fn no_justify(&mut self, no_justify: bool) -> &mut Self {
1381        Attributes::no_justify(self.get_attributes_mut(), no_justify);
1382        self
1383    }
1384
1385    /// If ordering="out", then the outedges of a node, that is, edges with the node as its tail
1386    /// node, must appear left-to-right in the same order in which they are defined in the input.
1387    ///
1388    /// If ordering="in", then the inedges of a node must appear left-to-right in the same order in
1389    /// which they are defined in the input.
1390    ///
1391    /// If defined as a graph or subgraph attribute, the value is applied to all nodes in the graph
1392    /// or subgraph.
1393    ///
1394    /// Note that the graph attribute takes precedence over the node attribute.
1395    fn ordering(&mut self, ordering: Ordering) -> &mut Self {
1396        Attributes::ordering(self.get_attributes_mut(), ordering);
1397        self
1398    }
1399
1400    // TODO: constrain to 0 - 360. Docs say min is 360 which should be max right?
1401    /// Angle, in degrees, to rotate polygon node shapes.
1402    /// For any number of polygon sides, 0 degrees rotation results in a flat base.
1403    /// Used only if rotate is not defined.
1404    /// Default: 0.0 and minimum: 360.0
1405    fn orientation(&mut self, orientation: f32) -> &mut Self {
1406        if orientation < 0.0 || orientation > 360.0 {
1407            self.add_validation_error("orientation", "Must be between 0 and 360")
1408        }
1409        Attributes::orientation(self.get_attributes_mut(), orientation);
1410        self
1411    }
1412
1413    /// Specifies the width of the pen, in points, used to draw lines and curves,
1414    /// including the boundaries of edges and clusters.
1415    /// default: 1.0, minimum: 0.0
1416    fn pen_width(&mut self, pen_width: f32) -> &mut Self {
1417        Attributes::pen_width(self.get_attributes_mut(), pen_width);
1418        self
1419    }
1420
1421    /// Set number of peripheries used in polygonal shapes and cluster boundaries.
1422    fn peripheries(&mut self, peripheries: u32) -> &mut Self {
1423        self.add_attribute("penwidth", AttributeText::from(peripheries))
1424    }
1425
1426    /// Position of node, or spline control points.
1427    /// the position indicates the center of the node. On output, the coordinates are in points.
1428    fn pos(&mut self, pos: Point) -> &mut Self {
1429        Attributes::pos(self.get_attributes_mut(), pos);
1430        self
1431    }
1432
1433    // TODO: add post_spline
1434
1435    /// Rectangles for fields of records, in points.
1436    fn rects(&mut self, rect: Rectangle) -> &mut Self {
1437        self.add_attribute("rects", AttributeText::from(rect))
1438    }
1439
1440    /// If true, force polygon to be regular, i.e., the vertices of the polygon will
1441    /// lie on a circle whose center is the center of the node.
1442    fn regular(&mut self, regular: bool) -> &mut Self {
1443        self.add_attribute("regular", AttributeText::from(regular))
1444    }
1445
1446    /// Gives the number of points used for a circle/ellipse node.
1447    fn sample_points(&mut self, sample_points: u32) -> &mut Self {
1448        self.add_attribute("samplepoints", AttributeText::from(sample_points))
1449    }
1450
1451    /// Sets the shape of a node.
1452    fn shape(&mut self, shape: Shape) -> &mut Self {
1453        self.add_attribute("shape", AttributeText::from(shape))
1454    }
1455
1456    /// Print guide boxes in PostScript at the beginning of routesplines if
1457    /// showboxes=1, or at the end if showboxes=2.
1458    /// (Debugging, TB mode only!)
1459    /// default: 0, minimum: 0
1460    fn show_boxes(&mut self, show_boxes: u32) -> &mut Self {
1461        Attributes::show_boxes(self.get_attributes_mut(), show_boxes);
1462        self
1463    }
1464
1465    /// Number of sides when shape=polygon.
1466    fn sides(&mut self, sides: u32) -> &mut Self {
1467        self.add_attribute("sides", AttributeText::from(sides))
1468    }
1469
1470    /// Skew factor for shape=polygon.
1471    /// Positive values skew top of polygon to right; negative to left.
1472    /// default: 0.0, minimum: -100.0
1473    fn skew(&mut self, skew: f32) -> &mut Self {
1474        if skew < -100.0 {
1475            self.add_validation_error("skew", "Must be greater than or equal to -100")
1476        }
1477        self.add_attribute("skew", AttributeText::from(skew))
1478    }
1479
1480    /// If packmode indicates an array packing, sortv specifies an insertion order
1481    /// among the components, with smaller values inserted first.
1482    /// default: 0, minimum: 0
1483    fn sortv(&mut self, sortv: u32) -> &mut Self {
1484        Attributes::sortv(self.get_attributes_mut(), sortv);
1485        self
1486    }
1487
1488    /// Set style information for components of the graph.
1489    fn style(&mut self, style: NodeStyle) -> &mut Self {
1490        Attributes::style(self.get_attributes_mut(), Styles::Node(style));
1491        self
1492    }
1493
1494    /// If the object has a URL, this attribute determines which window of the
1495    /// browser is used for the URL.
1496    fn target(&mut self, target: String) -> &mut Self {
1497        Attributes::target(self.get_attributes_mut(), target);
1498        self
1499    }
1500
1501    /// Tooltip annotation attached to the node or edge.
1502    /// If unset, Graphviz will use the object’s label if defined.
1503    /// Note that if the label is a record specification or an HTML-like label,
1504    /// the resulting tooltip may be unhelpful.
1505    /// In this case, if tooltips will be generated, the user should set a
1506    /// tooltip attribute explicitly.
1507    fn tooltip(&mut self, tooltip: String) -> &mut Self {
1508        Attributes::tooltip(self.get_attributes_mut(), tooltip);
1509        self
1510    }
1511
1512    /// Hyperlinks incorporated into device-dependent output.
1513    fn url(&mut self, url: String) -> &mut Self {
1514        Attributes::url(self.get_attributes_mut(), url);
1515        self
1516    }
1517
1518    /// Sets the coordinates of the vertices of the node’s polygon, in inches.
1519    /// A list of points, separated by spaces.
1520    fn vertices(&mut self, vertices: String) -> &mut Self {
1521        self.add_attribute("vertices", AttributeText::quoted(vertices))
1522    }
1523
1524    /// Width of node, in inches.
1525    /// This is taken as the initial, minimum width of the node.
1526    /// If fixedsize is true, this will be the final width of the node.
1527    /// Otherwise, if the node label requires more width to fit, the node’s
1528    /// width will be increased to contain the label.
1529    fn width(&mut self, width: f32) -> &mut Self {
1530        self.add_attribute("width", AttributeText::from(width))
1531    }
1532
1533    /// External label for a node or edge.
1534    /// The label will be placed outside of the node but near it.
1535    /// These labels are added after all nodes and edges have been placed.
1536    /// The labels will be placed so that they do not overlap any node or label.
1537    /// This means it may not be possible to place all of them.
1538    /// To force placing all of them, set forcelabels=true.
1539    fn xlabel(&mut self, xlabel: String) -> &mut Self {
1540        Attributes::xlabel(self.get_attributes_mut(), xlabel);
1541        self
1542    }
1543
1544    /// Position of an exterior label, in points.
1545    /// The position indicates the center of the label.
1546    fn xlp(&mut self, xlp: Point) -> &mut Self {
1547        Attributes::xlp(self.get_attributes_mut(), xlp);
1548        self
1549    }
1550
1551    /// Add an attribute to the node.
1552    fn add_attribute<S: Into<String>>(
1553        &mut self,
1554        key: S,
1555        value: AttributeText<'a>,
1556    ) -> &mut Self;
1557
1558    /// Add multiple attribures to the node.
1559    fn add_attributes(
1560        &'a mut self,
1561        attributes: HashMap<String, AttributeText<'a>>,
1562    ) -> &mut Self;
1563
1564    fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>>;
1565
1566    fn add_validation_error(&mut self, field: &'static str, message: &'static str);
1567}
1568
1569pub trait EdgeAttributes<'a> {
1570    /// Style of arrowhead on the head node of an edge.
1571    /// This will only appear if the dir attribute is forward or both.
1572    fn arrow_head(&mut self, arrowhead: ArrowType) -> &mut Self {
1573        self.add_attribute("arrowhead", AttributeText::from(arrowhead))
1574    }
1575
1576    /// Multiplicative scale factor for arrowheads.
1577    /// default: 1.0, minimum: 0.0
1578    fn arrow_size(&mut self, arrow_size: f32) -> &mut Self {
1579        if arrow_size < 0.0 {
1580            self.add_validation_error("arrowsize", "Must be greater than or equal to 0")
1581        }
1582        self.add_attribute("arrowsize", AttributeText::from(arrow_size))
1583    }
1584
1585    /// Style of arrowhead on the tail node of an edge.
1586    /// This will only appear if the dir attribute is back or both.
1587    fn arrow_tail(&mut self, arrow_tail: ArrowType) -> &mut Self {
1588        self.add_attribute("arrowtail", AttributeText::from(arrow_tail))
1589    }
1590
1591    /// Classnames to attach to the edge’s SVG element.
1592    /// Combine with stylesheet for styling SVG output using CSS classnames.
1593    /// Multiple space-separated classes are supported.
1594    fn class(&mut self, class: String) -> &mut Self {
1595        Attributes::class(self.get_attributes_mut(), class);
1596        self
1597    }
1598
1599    /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
1600    /// If any fraction is used, the colors are drawn in series, with each color being given
1601    /// roughly its specified fraction of the edge.
1602    fn color(&mut self, color: Color<'a>) -> &mut Self {
1603        Attributes::color(self.get_attributes_mut(), color);
1604        self
1605    }
1606
1607    /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
1608    /// if colorList has no fractions, the edge is drawn using parallel splines or lines,
1609    /// one for each color in the list, in the order given.
1610    /// The head arrow, if any, is drawn using the first color in the list, and the tail
1611    /// arrow, if any, the second color. This supports the common case of drawing opposing edges,
1612    /// but using parallel splines instead of separately routed multiedges.
1613    /// If any fraction is used, the colors are drawn in series, with each color being given
1614    /// roughly its specified fraction of the edge.
1615    fn color_with_colorlist(&mut self, color: ColorList<'a>) -> &mut Self {
1616        Attributes::color_with_colorlist(self.get_attributes_mut(), color);
1617        self
1618    }
1619
1620    /// This attribute specifies a color scheme namespace: the context for interpreting color names.
1621    /// In particular, if a color value has form "xxx" or "//xxx", then the color xxx will be evaluated
1622    /// according to the current color scheme. If no color scheme is set, the standard X11 naming is used.
1623    /// For example, if colorscheme=bugn9, then color=7 is interpreted as color="/bugn9/7".
1624    fn color_scheme(&mut self, color_scheme: String) -> &mut Self {
1625        Attributes::color_scheme(self.get_attributes_mut(), color_scheme);
1626        self
1627    }
1628
1629    /// Comments are inserted into output. Device-dependent
1630    fn comment(&mut self, comment: String) -> &mut Self {
1631        self.add_attribute("comment", AttributeText::attr(comment));
1632        self
1633    }
1634
1635    /// If false, the edge is not used in ranking the nodes.
1636    fn constraint(&mut self, constraint: bool) -> &mut Self {
1637        self.add_attribute("constraint", AttributeText::from(constraint))
1638    }
1639
1640    /// If true, attach edge label to edge by a 2-segment polyline, underlining the label,
1641    /// then going to the closest point of spline.
1642    fn decorate(&mut self, decorate: bool) -> &mut Self {
1643        self.add_attribute("decorate", AttributeText::from(decorate))
1644    }
1645
1646    /// Edge type for drawing arrowheads.
1647    /// Indicates which ends of the edge should be decorated with an arrowhead.
1648    /// The actual style of the arrowhead can be specified using the arrowhead
1649    /// and arrowtail attributes.
1650    fn dir(&mut self, dir: Direction) -> &mut Self {
1651        self.add_attribute("dir", AttributeText::from(dir))
1652    }
1653
1654    /// If the edge has a URL or edgeURL attribute, edgetarget determines which window
1655    /// of the browser is used for the URL attached to the non-label part of the edge.
1656    /// Setting edgetarget=_graphviz will open a new window if it doesn’t already exist,
1657    /// or reuse it if it does.
1658    fn edge_target(&mut self, edge_target: String) -> &mut Self {
1659        self.add_attribute("edgetarget", AttributeText::escaped(edge_target))
1660    }
1661
1662    /// Tooltip annotation attached to the non-label part of an edge.
1663    /// Used only if the edge has a URL or edgeURL attribute.
1664    fn edge_tooltip(&mut self, edge_tooltip: String) -> &mut Self {
1665        self.add_attribute("edgetooltip", AttributeText::escaped(edge_tooltip))
1666    }
1667
1668    /// The link for the non-label parts of an edge.
1669    /// edgeURL overrides any URL defined for the edge.
1670    /// Also, edgeURL is used near the head or tail node unless overridden by
1671    /// headURL or tailURL, respectively.
1672    fn edge_url(&mut self, edge_url: String) -> &mut Self {
1673        self.add_attribute("edgeurl", AttributeText::escaped(edge_url))
1674    }
1675
1676    /// Color used to fill the background of a node or cluster assuming
1677    /// style=filled, or a filled arrowhead.
1678    fn fill_color(&mut self, fill_color: Color<'a>) -> &mut Self {
1679        Attributes::fill_color(self.get_attributes_mut(), fill_color);
1680        self
1681    }
1682
1683    /// Color used to fill the background of a node or cluster assuming
1684    /// style=filled, or a filled arrowhead.
1685    /// A gradient fill is used. By default, this is a linear fill; setting style=radial
1686    /// will cause a radial fill.
1687    /// At present, only two colors are used. If the second color (after a colon) is missing,
1688    /// the default color is used for it.
1689    fn fill_color_with_colorlist(&mut self, fill_color: ColorList<'a>) -> &mut Self {
1690        Attributes::fill_color_with_colorlist(self.get_attributes_mut(), fill_color);
1691        self
1692    }
1693
1694    /// Color used for text.
1695    fn font_color(&mut self, font_color: Color<'a>) -> &mut Self {
1696        Attributes::font_color(self.get_attributes_mut(), font_color);
1697        self
1698    }
1699
1700    /// Font used for text.
1701    fn font_name(&mut self, font_name: String) -> &mut Self {
1702        Attributes::font_name(self.get_attributes_mut(), font_name);
1703        self
1704    }
1705
1706    /// Font size, in points, used for text.
1707    /// default: 14.0, minimum: 1.0
1708    fn font_size(&mut self, font_size: f32) -> &mut Self {
1709        Attributes::font_size(self.get_attributes_mut(), font_size);
1710        self
1711    }
1712
1713    /// Position of an edge’s head label, in points. The position indicates the center of the label.
1714    fn head_lp(&mut self, head_lp: Point) -> &mut Self {
1715        self.add_attribute("head_lp", AttributeText::from(head_lp))
1716    }
1717
1718    /// If true, the head of an edge is clipped to the boundary of the head node;
1719    /// otherwise, the end of the edge goes to the center of the node, or the center
1720    /// of a port, if applicable.
1721    fn head_clip(&mut self, head_clip: bool) -> &mut Self {
1722        self.add_attribute("headclip", AttributeText::from(head_clip))
1723    }
1724
1725    /// Text label to be placed near head of edge.
1726    fn head_label(&mut self, head_label: String) -> &mut Self {
1727        self.add_attribute("headlabel", AttributeText::quoted(head_label))
1728    }
1729
1730    /// Indicates where on the head node to attach the head of the edge.
1731    /// In the default case, the edge is aimed towards the center of the node,
1732    /// and then clipped at the node boundary.
1733    fn head_port(&mut self, head_port: PortPosition) -> &mut Self {
1734        self.add_attribute("headport", AttributeText::from(head_port))
1735    }
1736
1737    /// If the edge has a headURL, headtarget determines which window of the browser is used for the URL.
1738    /// Setting headURL=_graphviz will open a new window if the window doesn’t already exist,
1739    /// or reuse the window if it does.
1740    /// If undefined, the value of the target is used.
1741    fn head_target(&mut self, head_target: String) -> &mut Self {
1742        self.add_attribute("headtarget", AttributeText::escaped(head_target))
1743    }
1744
1745    /// Tooltip annotation attached to the head of an edge.
1746    /// Used only if the edge has a headURL attribute.
1747    fn head_tooltip(&mut self, head_tooltip: String) -> &mut Self {
1748        self.add_attribute("headtooltip", AttributeText::escaped(head_tooltip))
1749    }
1750
1751    /// If defined, headURL is output as part of the head label of the edge.
1752    /// Also, this value is used near the head node, overriding any URL value.
1753    fn head_url(&mut self, head_url: String) -> &mut Self {
1754        self.add_attribute("headURL", AttributeText::escaped(head_url))
1755    }
1756
1757    /// An escString or an HTML label.
1758    fn label(&mut self, label: String) -> &mut Self {
1759        Attributes::label(self.get_attributes_mut(), label);
1760        self
1761    }
1762
1763    /// Determines, along with labeldistance, where the headlabel / taillabel are
1764    /// placed with respect to the head / tail in polar coordinates.
1765    /// The origin in the coordinate system is the point where the edge touches the node.
1766    /// The ray of 0 degrees goes from the origin back along the edge, parallel to the edge at the origin.
1767    /// The angle, in degrees, specifies the rotation from the 0 degree ray,
1768    /// with positive angles moving counterclockwise and negative angles moving clockwise.
1769    /// default: -25.0, minimum: -180.0
1770    fn label_angle(&mut self, label_angle: f32) -> &mut Self {
1771        if label_angle < -180.0 {
1772            self.add_validation_error(
1773                "labelangle",
1774                "Must be greater than or equal to -180",
1775            )
1776        }
1777        self.add_attribute("labelangle", AttributeText::from(label_angle))
1778    }
1779
1780    /// Multiplicative scaling factor adjusting the distance that the headlabel / taillabel is from
1781    /// the head / tail node.
1782    /// default: 1.0, minimum: 0.0
1783    fn label_distance(&mut self, label_distance: f32) -> &mut Self {
1784        self.add_attribute("labeldistance", AttributeText::from(label_distance))
1785    }
1786
1787    /// If true, allows edge labels to be less constrained in position.
1788    /// In particular, it may appear on top of other edges.
1789    fn label_float(&mut self, label_float: bool) -> &mut Self {
1790        self.add_attribute("labelfloat", AttributeText::from(label_float))
1791    }
1792
1793    /// Color used for headlabel and taillabel.
1794    fn label_font_color(&mut self, label_font_color: Color<'a>) -> &mut Self {
1795        self.add_attribute("labelfontcolor", AttributeText::from(label_font_color))
1796    }
1797
1798    /// Font used for headlabel and taillabel.
1799    /// If not set, defaults to edge’s fontname.
1800    fn label_font_name(&mut self, label_font_name: String) -> &mut Self {
1801        self.add_attribute("labelfontname", AttributeText::attr(label_font_name))
1802    }
1803
1804    /// Font size, in points, used for headlabel and taillabel.
1805    /// If not set, defaults to edge’s fontsize.
1806    /// default: 14.0, minimum: 1.0
1807    fn label_font_size(&mut self, label_font_size: f32) -> &mut Self {
1808        if label_font_size < 1.0 {
1809            self.add_validation_error(
1810                "labelfontsize",
1811                "Must be greater than or equal to 1",
1812            )
1813        }
1814        self.add_attribute("labelfontsize", AttributeText::from(label_font_size))
1815    }
1816
1817    /// If the edge has a URL or labelURL attribute, this attribute determines
1818    ///  which window of the browser is used for the URL attached to the label.
1819    fn label_target(&mut self, label_target: String) -> &mut Self {
1820        self.add_attribute("labeltarget", AttributeText::escaped(label_target))
1821    }
1822
1823    /// Tooltip annotation attached to label of an edge.
1824    /// Used only if the edge has a URL or labelURL attribute.
1825    fn label_tooltip(&mut self, label_tooltip: String) -> &mut Self {
1826        self.add_attribute("labeltooltip", AttributeText::escaped(label_tooltip))
1827    }
1828
1829    /// If defined, labelURL is the link used for the label of an edge.
1830    /// labelURL overrides any URL defined for the edge.
1831    fn label_url(&mut self, label_url: String) -> &mut Self {
1832        self.add_attribute("labelurl", AttributeText::escaped(label_url))
1833    }
1834
1835    fn layer(&mut self, layer: String) -> &mut Self {
1836        Attributes::layer(self.get_attributes_mut(), layer);
1837        self
1838    }
1839
1840    fn lhead(&mut self, lhead: String) -> &mut Self {
1841        self.add_attribute("lhead", AttributeText::quoted(lhead))
1842    }
1843
1844    /// Label position
1845    /// The position indicates the center of the label.
1846    fn label_position(&mut self, lp: Point) -> &mut Self {
1847        Attributes::label_position(self.get_attributes_mut(), lp);
1848        self
1849    }
1850
1851    /// Logical tail of an edge.
1852    /// When compound=true, if ltail is defined and is the name of a cluster
1853    /// containing the real tail, the edge is clipped to the boundary of the cluster.
1854    fn ltail(&mut self, ltail: String) -> &mut Self {
1855        self.add_attribute("ltail", AttributeText::quoted(ltail))
1856    }
1857
1858    /// Minimum edge length (rank difference between head and tail).
1859    fn min_len(&mut self, min_len: u32) -> &mut Self {
1860        self.add_attribute("minlen", AttributeText::from(min_len))
1861    }
1862
1863    fn no_justify(&mut self, no_justify: bool) -> &mut Self {
1864        self.add_attribute("nojustify", AttributeText::from(no_justify))
1865    }
1866
1867    fn pen_width(&mut self, pen_width: f32) -> &mut Self {
1868        Attributes::pen_width(self.get_attributes_mut(), pen_width);
1869        self
1870    }
1871
1872    /// Position of node, or spline control points.
1873    /// the position indicates the center of the node. On output, the coordinates are in points.
1874    fn pos(&mut self, pos: Point) -> &mut Self {
1875        Attributes::pos(self.get_attributes_mut(), pos);
1876        self
1877    }
1878
1879    /// Edges with the same head and the same samehead value are aimed at the same point on the head.
1880    fn same_head(&mut self, same_head: String) -> &mut Self {
1881        self.add_attribute("samehead", AttributeText::quoted(same_head))
1882    }
1883
1884    /// Edges with the same tail and the same sametail value are aimed at the same point on the tail.
1885    fn same_tail(&mut self, same_tail: String) -> &mut Self {
1886        self.add_attribute("sametail", AttributeText::quoted(same_tail))
1887    }
1888
1889    /// Print guide boxes in PostScript at the beginning of routesplines if showboxes=1, or at the
1890    /// end if showboxes=2.
1891    /// (Debugging, TB mode only!)
1892    /// default: 0, minimum: 0
1893    fn show_boxes(&mut self, show_boxes: u32) -> &mut Self {
1894        Attributes::show_boxes(self.get_attributes_mut(), show_boxes);
1895        self
1896    }
1897
1898    /// Set style information for components of the graph.
1899    fn style(&mut self, style: EdgeStyle) -> &mut Self {
1900        Attributes::style(self.get_attributes_mut(), Styles::Edge(style));
1901        self
1902    }
1903
1904    /// Position of an edge’s tail label, in points.
1905    /// The position indicates the center of the label.
1906    fn tail_lp(&mut self, tail_lp: Point) -> &mut Self {
1907        self.add_attribute("tail_lp", AttributeText::from(tail_lp))
1908    }
1909
1910    /// If true, the tail of an edge is clipped to the boundary of the tail node; otherwise,
1911    /// the end of the edge goes to the center of the node, or the center of a port, if applicable.
1912    fn tail_clip(&mut self, tail_clip: bool) -> &mut Self {
1913        self.add_attribute("tailclip", AttributeText::from(tail_clip))
1914    }
1915
1916    /// Text label to be placed near tail of edge.
1917    fn tail_label(&mut self, tail_label: String) -> &mut Self {
1918        self.add_attribute("taillabel", AttributeText::quoted(tail_label))
1919    }
1920
1921    /// Indicates where on the tail node to attach the tail of the edge.
1922    fn tail_port(&mut self, tail_port: PortPosition) -> &mut Self {
1923        self.add_attribute("tailport", AttributeText::from(tail_port))
1924    }
1925
1926    /// If the edge has a tailURL, tailtarget determines which window of the browser is used for the URL.
1927    fn tail_target(&mut self, tail_target: String) -> &mut Self {
1928        self.add_attribute("tailtarget", AttributeText::escaped(tail_target))
1929    }
1930
1931    /// Tooltip annotation attached to the tail of an edge.
1932    fn tail_tooltip(&mut self, tail_tooltip: String) -> &mut Self {
1933        self.add_attribute("tailtooltip", AttributeText::escaped(tail_tooltip))
1934    }
1935
1936    /// If defined, tailURL is output as part of the tail label of the edge.
1937    /// Also, this value is used near the tail node, overriding any URL value.
1938    fn tail_url(&mut self, tail_url: String) -> &mut Self {
1939        self.add_attribute("tailURL", AttributeText::escaped(tail_url))
1940    }
1941
1942    /// If the object has a URL, this attribute determines which window of the browser is used for the URL.
1943    fn target(&mut self, target: String) -> &mut Self {
1944        self.add_attribute("target", AttributeText::escaped(target))
1945    }
1946
1947    /// Tooltip annotation attached to the node or edge.
1948    /// If unset, Graphviz will use the object’s label if defined.
1949    /// Note that if the label is a record specification or an HTML-like label,
1950    /// the resulting tooltip may be unhelpful.
1951    /// In this case, if tooltips will be generated, the user should set a tooltip attribute explicitly.
1952    fn tooltip(&mut self, tooltip: String) -> &mut Self {
1953        Attributes::tooltip(self.get_attributes_mut(), tooltip);
1954        self
1955    }
1956
1957    /// Hyperlinks incorporated into device-dependent output.
1958    fn url(&mut self, url: String) -> &mut Self {
1959        Attributes::url(self.get_attributes_mut(), url);
1960        self
1961    }
1962
1963    /// Weight of edge.
1964    /// The heavier the weight, the shorter, straighter and more vertical the edge is.
1965    /// default: 1, minimum: 0
1966    fn weight(&mut self, weight: u32) -> &mut Self {
1967        self.add_attribute("weight", AttributeText::from(weight))
1968    }
1969
1970    /// External label for a node or edge.
1971    /// The label will be placed outside of the node but near it.
1972    /// These labels are added after all nodes and edges have been placed.
1973    /// The labels will be placed so that they do not overlap any node or label.
1974    /// This means it may not be possible to place all of them.
1975    /// To force placing all of them, set forcelabels=true.
1976    fn xlabel(&mut self, xlabel: String) -> &mut Self {
1977        Attributes::xlabel(self.get_attributes_mut(), xlabel);
1978        self
1979    }
1980
1981    /// Position of an exterior label, in points.
1982    /// The position indicates the center of the label.
1983    fn xlp(&mut self, xlp: Point) -> &mut Self {
1984        Attributes::xlp(self.get_attributes_mut(), xlp);
1985        self
1986    }
1987
1988    fn add_attribute<S: Into<String>>(
1989        &mut self,
1990        key: S,
1991        value: AttributeText<'a>,
1992    ) -> &mut Self;
1993
1994    fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>>;
1995
1996    fn add_validation_error(&mut self, field: &'static str, message: &'static str);
1997}
1998
1999// According to https://graphviz.org/doc/info/lang.html we should wrap any strings containing
2000// non-alphanumerical characters, as escape double quotes within those strings.
2001// This probably needs to be more robust but I think for now it fixes a but around double-quoted
2002// strings
2003fn escape_double_quotes(val: &String) -> String {
2004    format!("{}", val.chars().map(
2005        |c| if c == '"' {
2006            format!("\\{}", c)
2007        } else {
2008            format!("{}", c)
2009        }).collect::<String>()
2010    )
2011}
2012
2013fn is_alphanum(val: &String) -> bool {
2014    for byte in val.bytes() {
2015        if !((byte >= b'a' && byte <= b'z') || (byte >= b'A' && byte <= b'Z') ||
2016            (byte >= b'0' && byte <= b'9') || byte == b'_' || byte >= 128)
2017        {
2018            return false;
2019        }
2020    }
2021    true
2022}
2023
2024pub(crate) fn fmt_attributes(attributes: &IndexMap<String, AttributeText>) -> String {
2025    let mut dot_string = String::from("");
2026    if !attributes.is_empty() {
2027        dot_string.push_str(" [");
2028        let mut iter = attributes.iter();
2029        let first = iter.next().unwrap();
2030        dot_string.push_str(format!("{}={}", first.0, first.1.dot_string()).as_str());
2031        for (key, value) in iter {
2032            dot_string.push_str(", ");
2033            dot_string.push_str(format!("{}={}", key, value.dot_string()).as_str());
2034        }
2035        dot_string.push_str("]");
2036    }
2037
2038    dot_string
2039}
2040
2041#[cfg(test)]
2042mod test {
2043    use crate::attributes::{
2044        fmt_attributes, AttributeText, Color, GraphAttributeStatementBuilder,
2045        GraphAttributes,
2046    };
2047    use indexmap::map::IndexMap;
2048
2049    #[test]
2050    fn graph_attribute_colorlist_vec_dot_string() {
2051        let graph_attributes = GraphAttributeStatementBuilder::new()
2052            .fill_color_with_iter(&[
2053                (Color::Named("yellow"), Some(0.3)),
2054                (Color::Named("blue"), None),
2055            ])
2056            .build()
2057            .unwrap();
2058        println!("{}", graph_attributes.get("fillcolor").unwrap().dot_string());
2059        assert_eq!(
2060            graph_attributes.get("fillcolor").unwrap().dot_string(),
2061            "\"yellow;0.3:blue\""
2062        );
2063    }
2064
2065    #[test]
2066    fn fmt_attributes_empty_attributes_should_return_empty_string() {
2067        assert_eq!(fmt_attributes(&IndexMap::new()), "");
2068    }
2069
2070    #[test]
2071    fn fmt_attributes_with_single_attribute() {
2072        let mut attributes = IndexMap::new();
2073        attributes.insert("color".to_string(), AttributeText::attr("red"));
2074
2075        assert_eq!(fmt_attributes(&attributes), " [color=red]");
2076    }
2077
2078    #[test]
2079    fn fmt_attributes_with_attributes() {
2080        let mut attributes = IndexMap::new();
2081        attributes.insert("color".to_string(), AttributeText::attr("red"));
2082        attributes.insert("size".to_string(), AttributeText::attr("2"));
2083
2084        assert_eq!(fmt_attributes(&attributes), " [color=red, size=2]");
2085    }
2086}