railroad/
lib.rs

1// MIT License
2//
3// Copyright (c) Lukas Lueg (lukas.lueg@gmail.com)
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22//
23//! A library to create syntax ("railroad") diagrams as Scalable Vector Graphics (SVG).
24//!
25//! Railroad diagrams are a graphical way to represent context-free grammar.
26//! Every diagram has exactly one starting- and one end-point; everything that
27//! belongs to the described language is represented by one of the possible paths
28//! between those points.
29//!
30//! Using this library, diagrams are created by primitives which implemented `Node`.
31//! Primitives are combined into more complex strctures by wrapping simple elements into more
32//! complex ones.
33//!
34//! ```rust
35//! use railroad::*;
36//!
37//! // This diagram will be a (horizontal) sequence of simple elements
38//! let mut seq: Sequence<Box<dyn Node>> = Sequence::default();
39//! seq.push(Box::new(Start))
40//!    .push(Box::new(Terminal::new("BEGIN".to_owned())))
41//!    .push(Box::new(NonTerminal::new("syntax".to_owned())))
42//!    .push(Box::new(End));
43//!
44//! // The library only computes the diagram's geometry; we use CSS for layout.
45//! let mut dia = Diagram::new_with_stylesheet(seq, &Stylesheet::Light);
46//!
47//! // A `Node`'s `fmt::Display` is its SVG.
48//! println!("<html>{}</html>", dia);
49//! ```
50
51use std::{
52    cmp,
53    collections::{self, HashMap},
54    fmt, io, iter,
55};
56
57pub mod notactuallysvg;
58pub use crate::notactuallysvg as svg;
59use crate::svg::HDir;
60
61#[cfg(feature = "resvg")]
62pub mod render;
63
64#[cfg(feature = "resvg")]
65pub use resvg;
66
67#[doc = include_str!("../README.md")]
68#[allow(dead_code)]
69type _READMETEST = ();
70
71/// Used as a form of scale throughout geometry calculations. Smaller values result in more compact
72/// diagrams.
73const ARC_RADIUS: i64 = 12;
74
75/// Determine the width some text will have when rendered.
76///
77/// The geometry of some primitives depends on this, which is hacky in the first place.
78fn text_width(s: &str) -> usize {
79    use unicode_width::UnicodeWidthStr;
80    // Use a fudge-factor of 1.05
81    s.width() + (s.width() / 20)
82}
83
84/// Pre-defined stylesheets
85/// ```rust
86/// use railroad::*;
87///
88/// let mut seq: Sequence::<Box<dyn Node>> = Sequence::default();
89/// seq.push(Box::new(Start))
90///    .push(Box::new(Terminal::new("Foobar".to_owned())))
91///    .push(Box::new(End));
92///
93/// let dia = Diagram::new_with_stylesheet(seq, &Stylesheet::Light);
94/// println!("{}", dia);
95/// ```
96#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
97#[non_exhaustive]
98pub enum Stylesheet {
99    /// The default stylesheet
100    #[default]
101    Light,
102    Dark,
103    /// Variation of the `Light`-theme, compatible with what can be rendered when using `resvg`.
104    LightRendersafe,
105    /// Variation of the `Dark`-theme, compatible with what can be rendered when using `resvg`.
106    DarkRendersafe,
107}
108
109impl Stylesheet {
110    /// Switch this stylesheet to it's "dark" variant, preserving render-safety.
111    #[must_use]
112    pub const fn to_dark(&self) -> Self {
113        match self {
114            Self::Light | Self::Dark => Self::Dark,
115            Self::LightRendersafe | Self::DarkRendersafe => Self::DarkRendersafe,
116        }
117    }
118
119    /// Switch this stylesheet to it's "light" variant, preserving render-safety.
120    #[must_use]
121    pub const fn to_light(&self) -> Self {
122        match self {
123            Self::Light | Self::Dark => Self::Light,
124            Self::LightRendersafe | Self::DarkRendersafe => Self::LightRendersafe,
125        }
126    }
127
128    /// Returns `True` if this stylesheet is of a "light" variant.
129    #[must_use]
130    pub const fn is_light(&self) -> bool {
131        matches!(self, Self::Light | Self::LightRendersafe)
132    }
133
134    /// The CSS for this stylesheet.
135    #[must_use]
136    pub const fn stylesheet(self) -> &'static str {
137        match self {
138            Self::Light => include_str!("stylesheet_light.css"),
139            Self::Dark => include_str!("stylesheet_dark.css"),
140            Self::LightRendersafe => include_str!("stylesheet_light_safe.css"),
141            Self::DarkRendersafe => include_str!("stylesheet_dark_safe.css"),
142        }
143    }
144}
145
146/// Default Cascading Style Sheets for the resuling SVG.
147pub const DEFAULT_CSS: &str = Stylesheet::Light.stylesheet();
148
149/// A diagram is built from a set of primitives which implement `Node`.
150///
151/// A primitive is a geometric box, within which it can draw whatever it wants.
152/// Simple primitives (e.g. `Start`) have fixed width, height etc.. Complex
153/// primitives, which wrap other primitives (e.g. `Sequence`), use the methods
154/// defined here to compute their own geometry. When the time comes for a primitive
155/// to be drawn, the wrapping primitive computes the desired location of the wrapped
156/// primitive(s) and calls `.draw()` on them. It is the primitive's job
157/// to ensure that it uses only the space it announced.
158pub trait Node {
159    /// The vertical distance from this element's top to where the entering,
160    /// connecting path is drawn.
161    ///
162    /// By convention, the path connecting primitives enters from the left.
163    fn entry_height(&self) -> i64;
164
165    /// This primitives's total height.
166    fn height(&self) -> i64;
167
168    /// This primitive's total width.
169    fn width(&self) -> i64;
170
171    /// The vertical distance from the height of the connecting path to the bottom.
172    fn height_below_entry(&self) -> i64 {
173        self.height() - self.entry_height()
174    }
175
176    /// Draw this element as an `svg::Element`.
177    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element;
178}
179
180impl fmt::Debug for dyn Node {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        f.debug_struct("Node")
183            .field("entry_height", &self.entry_height())
184            .field("height", &self.height())
185            .field("width", &self.width())
186            .finish()
187    }
188}
189
190macro_rules! deref_impl {
191    ($($sig:tt)+) => {
192        impl $($sig)+ {
193            fn entry_height(&self) -> i64 {
194                (**self).entry_height()
195            }
196
197            fn height(&self) -> i64 {
198                (**self).height()
199            }
200
201            fn width(&self) -> i64 {
202                (**self).width()
203            }
204
205            fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
206                (**self).draw(x, y, h_dir)
207            }
208        }
209    };
210}
211deref_impl!(<'a, N> Node for &'a N where N: Node + ?Sized);
212deref_impl!(<'a, N> Node for &'a mut N where N: Node + ?Sized);
213deref_impl!(<N> Node for Box<N> where N: Node + ?Sized);
214deref_impl!(<N> Node for std::rc::Rc<N> where N: Node + ?Sized);
215deref_impl!(<N> Node for std::sync::Arc<N> where N: Node + ?Sized);
216
217/// Helper trait for collections of nodes.
218pub trait NodeCollection {
219    /// The maximum `entry_height()`-value.
220    fn max_entry_height(self) -> i64;
221
222    /// The maximum `height()`-value.
223    fn max_height(self) -> i64;
224
225    /// The maximum `height_below_entry()`-value.
226    fn max_height_below_entry(self) -> i64;
227
228    /// The maximum `width()`-value.
229    fn max_width(self) -> i64;
230
231    /// The sum of all `width()`-values.
232    fn total_width(self) -> i64;
233
234    /// The sum of all `height()`-values.
235    fn total_height(self) -> i64;
236}
237
238impl<I, N> NodeCollection for I
239where
240    I: IntoIterator<Item = N>,
241    N: Node,
242{
243    fn max_height_below_entry(self) -> i64 {
244        self.into_iter()
245            .map(|n| n.height_below_entry())
246            .max()
247            .unwrap_or_default()
248    }
249
250    fn max_entry_height(self) -> i64 {
251        self.into_iter()
252            .map(|n| n.entry_height())
253            .max()
254            .unwrap_or_default()
255    }
256
257    fn max_height(self) -> i64 {
258        self.into_iter()
259            .map(|n| n.height())
260            .max()
261            .unwrap_or_default()
262    }
263
264    fn max_width(self) -> i64 {
265        self.into_iter()
266            .map(|n| n.width())
267            .max()
268            .unwrap_or_default()
269    }
270
271    fn total_width(self) -> i64 {
272        self.into_iter().map(|n| n.width()).sum()
273    }
274
275    fn total_height(self) -> i64 {
276        self.into_iter().map(|n| n.height()).sum()
277    }
278}
279
280/// Possible targets for `Link`.
281#[derive(Debug, Default, Clone, Copy)]
282pub enum LinkTarget {
283    #[default]
284    Blank,
285    Parent,
286    Top,
287}
288
289/// Wraps another primitive, making it a clickable link to some URI.
290#[derive(Debug, Clone)]
291pub struct Link<N> {
292    inner: N,
293    uri: String,
294    target: Option<LinkTarget>,
295    attributes: HashMap<String, String>,
296}
297
298impl<N> Link<N> {
299    pub fn new(inner: N, uri: String) -> Self {
300        let mut l = Self {
301            inner,
302            uri,
303            target: None,
304            attributes: HashMap::default(),
305        };
306        l.attributes.insert("class".to_owned(), "link".to_owned());
307        l
308    }
309
310    /// Set the target-attribute
311    pub fn set_target(&mut self, target: Option<LinkTarget>) {
312        self.target = target;
313    }
314
315    /// Access an attribute on the main SVG-element that will be drawn.
316    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
317        self.attributes.entry(key)
318    }
319}
320
321impl<N> Node for Link<N>
322where
323    N: Node,
324{
325    fn entry_height(&self) -> i64 {
326        self.inner.entry_height()
327    }
328    fn height(&self) -> i64 {
329        self.inner.height()
330    }
331    fn width(&self) -> i64 {
332        self.inner.width()
333    }
334
335    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
336        let mut a = svg::Element::new("a")
337            .debug("Link", x, y, self)
338            .set("xlink:href", &self.uri);
339        a = match self.target {
340            Some(LinkTarget::Blank) => a.set("target", "_blank"),
341            Some(LinkTarget::Parent) => a.set("target", "_parent"),
342            Some(LinkTarget::Top) => a.set("target", "_top"),
343            None => a,
344        };
345        a.set_all(self.attributes.iter())
346            .add(self.inner.draw(x, y, h_dir))
347    }
348}
349
350/// A vertical group of unconnected elements.
351#[derive(Debug, Clone)]
352pub struct VerticalGrid<N> {
353    children: Vec<N>,
354    spacing: i64,
355    attributes: HashMap<String, String>,
356}
357
358impl<N> VerticalGrid<N> {
359    #[must_use]
360    pub fn new(children: Vec<N>) -> Self {
361        let mut v = Self {
362            children,
363            ..Self::default()
364        };
365        v.attributes
366            .insert("class".to_owned(), "verticalgrid".to_owned());
367        v
368    }
369
370    pub fn push(&mut self, child: N) -> &mut Self {
371        self.children.push(child);
372        self
373    }
374
375    #[must_use]
376    pub fn into_inner(self) -> Vec<N> {
377        self.children
378    }
379
380    /// Access an attribute on the main SVG-element that will be drawn.
381    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
382        self.attributes.entry(key)
383    }
384}
385
386impl<N> Default for VerticalGrid<N> {
387    fn default() -> Self {
388        Self {
389            children: Vec::default(),
390            spacing: ARC_RADIUS,
391            attributes: HashMap::default(),
392        }
393    }
394}
395
396impl<N> iter::FromIterator<N> for VerticalGrid<N> {
397    fn from_iter<T: IntoIterator<Item = N>>(iter: T) -> Self {
398        Self::new(iter.into_iter().collect())
399    }
400}
401
402impl<N: Node> Node for VerticalGrid<N> {
403    fn entry_height(&self) -> i64 {
404        0
405    }
406
407    fn height(&self) -> i64 {
408        self.children.iter().total_height()
409            + ((cmp::max(1, i64::try_from(self.children.len()).unwrap()) - 1) * self.spacing)
410    }
411
412    fn width(&self) -> i64 {
413        self.children.iter().max_width()
414    }
415
416    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
417        let mut g = svg::Element::new("g").set_all(self.attributes.iter());
418        let mut running_y = y;
419        for child in &self.children {
420            g.push(child.draw(x, running_y, h_dir));
421            running_y += child.height() + self.spacing;
422        }
423        g.debug("VerticalGrid", x, y, self)
424    }
425}
426
427/// A horizontal group of unconnected elements.
428#[derive(Debug, Clone)]
429pub struct HorizontalGrid<N> {
430    children: Vec<N>,
431    spacing: i64,
432    attributes: HashMap<String, String>,
433}
434
435impl<N> HorizontalGrid<N> {
436    #[must_use]
437    pub fn new(children: Vec<N>) -> Self {
438        let mut h = Self {
439            children,
440            ..Self::default()
441        };
442        h.attributes
443            .insert("class".to_owned(), "horizontalgrid".to_owned());
444        h
445    }
446
447    pub fn push(&mut self, child: N) -> &mut Self {
448        self.children.push(child);
449        self
450    }
451
452    #[must_use]
453    pub fn into_inner(self) -> Vec<N> {
454        self.children
455    }
456
457    /// Access an attribute on the main SVG-element that will be drawn.
458    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
459        self.attributes.entry(key)
460    }
461}
462
463impl<N> Default for HorizontalGrid<N> {
464    fn default() -> Self {
465        Self {
466            children: Vec::default(),
467            spacing: ARC_RADIUS,
468            attributes: HashMap::default(),
469        }
470    }
471}
472
473impl<N> iter::FromIterator<N> for HorizontalGrid<N> {
474    fn from_iter<T: IntoIterator<Item = N>>(iter: T) -> Self {
475        Self::new(iter.into_iter().collect())
476    }
477}
478
479impl<N> Node for HorizontalGrid<N>
480where
481    N: Node,
482{
483    fn entry_height(&self) -> i64 {
484        0
485    }
486
487    fn height(&self) -> i64 {
488        self.children.iter().max_height()
489    }
490
491    fn width(&self) -> i64 {
492        self.children.iter().total_width()
493            + ((cmp::max(1, i64::try_from(self.children.len()).unwrap()) - 1) * self.spacing)
494    }
495
496    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
497        let mut g = svg::Element::new("g").set_all(self.attributes.iter());
498        let mut running_x = x;
499        for child in &self.children {
500            g.push(child.draw(running_x, y, h_dir));
501            running_x += child.width() + self.spacing;
502        }
503        g.debug("HorizontalGrid", x, y, self)
504    }
505}
506
507/// A horizontal group of elements, connected from left to right.
508///
509/// Also see `Stack` for a vertical group of elements.
510#[derive(Debug, Clone)]
511pub struct Sequence<N> {
512    children: Vec<N>,
513    spacing: i64,
514}
515
516impl<N> Sequence<N> {
517    #[must_use]
518    pub fn new(children: Vec<N>) -> Self {
519        Self {
520            children,
521            ..Self::default()
522        }
523    }
524
525    pub fn push(&mut self, child: N) -> &mut Self {
526        self.children.push(child);
527        self
528    }
529
530    #[must_use]
531    pub fn into_inner(self) -> Vec<N> {
532        self.children
533    }
534}
535
536impl<N> Default for Sequence<N> {
537    fn default() -> Self {
538        Self {
539            children: Vec::new(),
540            spacing: 10,
541        }
542    }
543}
544
545impl<N> iter::FromIterator<N> for Sequence<N> {
546    fn from_iter<T: IntoIterator<Item = N>>(iter: T) -> Self {
547        Self::new(iter.into_iter().collect())
548    }
549}
550
551impl<N> Node for Sequence<N>
552where
553    N: Node,
554{
555    fn entry_height(&self) -> i64 {
556        self.children.iter().max_entry_height()
557    }
558
559    fn height(&self) -> i64 {
560        self.children.iter().max_entry_height() + self.children.iter().max_height_below_entry()
561    }
562
563    fn width(&self) -> i64 {
564        let l = self.children.len();
565        if l > 1 {
566            self.children.iter().total_width() + (i64::try_from(l).unwrap() - 1) * self.spacing
567        } else {
568            self.children.iter().total_width()
569        }
570    }
571
572    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
573        let mut g = svg::Element::new("g").set("class", "sequence");
574        let mut running_x = 0;
575        for child in &self.children {
576            g.push(child.draw(
577                x + running_x,
578                y + self.entry_height() - child.entry_height(),
579                h_dir,
580            ));
581            running_x += child.width() + self.spacing;
582        }
583
584        let mut running_x = x;
585        for child in self.children.iter().rev().skip(1).rev() {
586            g.push(
587                svg::PathData::new(h_dir)
588                    .move_to(running_x + child.width(), y + self.entry_height())
589                    .horizontal(self.spacing)
590                    .into_path(),
591            );
592            running_x += child.width() + self.spacing;
593        }
594        g.debug("Sequence", x, y, self)
595    }
596}
597
598/// A symbol indicating the logical end of a syntax-diagram via two vertical bars.
599#[derive(Debug, Clone, Default)]
600pub struct End;
601
602impl Node for End {
603    fn entry_height(&self) -> i64 {
604        10
605    }
606    fn height(&self) -> i64 {
607        20
608    }
609    fn width(&self) -> i64 {
610        20
611    }
612
613    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
614        svg::PathData::new(h_dir)
615            .move_to(x, y + 10)
616            .horizontal(20)
617            .move_rel(-10, -10)
618            .vertical(20)
619            .move_rel(10, -20)
620            .vertical(20)
621            .into_path()
622            .debug("End", x, y, self)
623    }
624}
625
626/// A symbol indicating the logical start of a syntax-diagram via a circle
627#[derive(Debug, Clone, Default)]
628pub struct SimpleStart;
629
630impl Node for SimpleStart {
631    fn entry_height(&self) -> i64 {
632        5
633    }
634    fn height(&self) -> i64 {
635        10
636    }
637    fn width(&self) -> i64 {
638        15
639    }
640
641    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
642        svg::PathData::new(h_dir)
643            .move_to(x, y + 5)
644            .arc(5, svg::Arc::SouthToEast)
645            .arc(5, svg::Arc::WestToSouth)
646            .arc(5, svg::Arc::NorthToWest)
647            .arc(5, svg::Arc::EastToNorth)
648            .move_rel(10, 0)
649            .horizontal(5)
650            .into_path()
651            .debug("SimpleStart", x, y, self)
652    }
653}
654
655/// A symbol indicating the logical end of a syntax-diagram via a circle
656#[derive(Debug, Clone, Default)]
657pub struct SimpleEnd;
658
659impl Node for SimpleEnd {
660    fn entry_height(&self) -> i64 {
661        5
662    }
663    fn height(&self) -> i64 {
664        10
665    }
666    fn width(&self) -> i64 {
667        15
668    }
669
670    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
671        svg::PathData::new(h_dir)
672            .move_to(x, y + 5)
673            .horizontal(5)
674            .arc(5, svg::Arc::SouthToEast)
675            .arc(5, svg::Arc::WestToSouth)
676            .arc(5, svg::Arc::NorthToWest)
677            .arc(5, svg::Arc::EastToNorth)
678            .into_path()
679            .debug("SimpleEnd", x, y, self)
680    }
681}
682
683/// A symbol indicating the logical start of a syntax-diagram via two vertical bars.
684#[derive(Debug, Clone, Default)]
685pub struct Start;
686
687impl Node for Start {
688    fn entry_height(&self) -> i64 {
689        10
690    }
691    fn height(&self) -> i64 {
692        20
693    }
694    fn width(&self) -> i64 {
695        20
696    }
697
698    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
699        svg::PathData::new(h_dir)
700            .move_to(x, y)
701            .vertical(20)
702            .move_rel(10, -20)
703            .vertical(20)
704            .move_rel(-10, -10)
705            .horizontal(20)
706            .into_path()
707            .debug("Start", x, y, self)
708    }
709}
710
711/// A `Terminal`-symbol, drawn as a rectangle with rounded corners.
712#[derive(Debug, Clone)]
713pub struct Terminal {
714    label: String,
715    attributes: HashMap<String, String>,
716}
717
718impl Terminal {
719    #[must_use]
720    pub fn new(label: String) -> Self {
721        let mut t = Self {
722            label,
723            attributes: HashMap::default(),
724        };
725        t.attributes
726            .insert("class".to_owned(), "terminal".to_owned());
727        t
728    }
729
730    /// Access an attribute on the main SVG-element that will be drawn.
731    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
732        self.attributes.entry(key)
733    }
734}
735
736impl Node for Terminal {
737    fn entry_height(&self) -> i64 {
738        11
739    }
740    fn height(&self) -> i64 {
741        self.entry_height() * 2
742    }
743    fn width(&self) -> i64 {
744        i64::try_from(text_width(&self.label)).unwrap() * 8 + 20
745    }
746
747    fn draw(&self, x: i64, y: i64, _: HDir) -> svg::Element {
748        let r = svg::Element::new("rect")
749            .set("x", &x)
750            .set("y", &y)
751            .set("height", &self.height())
752            .set("width", &self.width())
753            .set("rx", &10)
754            .set("ry", &10);
755        let t = svg::Element::new("text")
756            .set("x", &(x + self.width() / 2))
757            .set("y", &(y + self.entry_height() + 5))
758            .text(&self.label);
759        svg::Element::new("g")
760            .debug("terminal", x, y, self)
761            .set_all(self.attributes.iter())
762            .add(r)
763            .add(t)
764    }
765}
766
767/// A `NonTerminal`, drawn as a rectangle.
768#[derive(Debug, Clone)]
769pub struct NonTerminal {
770    label: String,
771    attributes: HashMap<String, String>,
772}
773
774impl NonTerminal {
775    #[must_use]
776    pub fn new(label: String) -> Self {
777        let mut nt = Self {
778            label,
779            attributes: HashMap::default(),
780        };
781        nt.attributes
782            .insert("class".to_owned(), "nonterminal".to_owned());
783        nt
784    }
785
786    /// Access an attribute on the main SVG-element that will be drawn.
787    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
788        self.attributes.entry(key)
789    }
790}
791
792impl Node for NonTerminal {
793    fn entry_height(&self) -> i64 {
794        11
795    }
796    fn height(&self) -> i64 {
797        self.entry_height() * 2
798    }
799    fn width(&self) -> i64 {
800        i64::try_from(text_width(&self.label)).unwrap() * 8 + 20
801    }
802
803    fn draw(&self, x: i64, y: i64, _: HDir) -> svg::Element {
804        svg::Element::new("g")
805            .debug("NonTerminal", x, y, self)
806            .set_all(self.attributes.iter())
807            .add(
808                svg::Element::new("rect")
809                    .set("x", &x)
810                    .set("y", &y)
811                    .set("height", &self.height())
812                    .set("width", &self.width()),
813            )
814            .add(
815                svg::Element::new("text")
816                    .set("x", &(x + self.width() / 2))
817                    .set("y", &(y + self.entry_height() + 5))
818                    .text(&self.label),
819            )
820    }
821}
822
823/// Wraps another element to make that element logically optional.
824///
825/// Draws a separate path above, which skips the given element.
826#[derive(Debug, Clone, Default)]
827pub struct Optional<N> {
828    inner: N,
829    attributes: HashMap<String, String>,
830}
831
832impl<N> Optional<N> {
833    pub fn new(inner: N) -> Self {
834        let mut o = Self {
835            inner,
836            attributes: HashMap::default(),
837        };
838        o.attributes
839            .insert("class".to_owned(), "optional".to_owned());
840        o
841    }
842
843    pub fn into_inner(self) -> N {
844        self.inner
845    }
846
847    /// Access an attribute on the main SVG-element that will be drawn.
848    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
849        self.attributes.entry(key)
850    }
851}
852
853impl<N> Node for Optional<N>
854where
855    N: Node,
856{
857    fn entry_height(&self) -> i64 {
858        ARC_RADIUS + cmp::max(ARC_RADIUS, self.inner.entry_height())
859    }
860
861    fn height(&self) -> i64 {
862        self.entry_height() + self.inner.height_below_entry()
863    }
864
865    fn width(&self) -> i64 {
866        ARC_RADIUS * 2 + self.inner.width() + ARC_RADIUS * 2
867    }
868
869    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
870        let i = self.inner.draw(
871            x + ARC_RADIUS * 2,
872            y + self.entry_height() - self.inner.entry_height(),
873            h_dir,
874        );
875
876        let v = svg::PathData::new(h_dir)
877            .move_to(x, y + self.entry_height())
878            .horizontal(ARC_RADIUS * 2)
879            .move_rel(-ARC_RADIUS * 2, 0)
880            .arc(ARC_RADIUS, svg::Arc::WestToNorth)
881            .vertical(cmp::min(0, -self.inner.entry_height() + ARC_RADIUS))
882            .arc(ARC_RADIUS, svg::Arc::SouthToEast)
883            .horizontal(self.inner.width())
884            .arc(ARC_RADIUS, svg::Arc::WestToSouth)
885            .vertical(cmp::max(0, self.inner.entry_height() - ARC_RADIUS))
886            .arc(ARC_RADIUS, svg::Arc::NorthToEast)
887            .horizontal(-ARC_RADIUS * 2)
888            .into_path();
889
890        svg::Element::new("g")
891            .debug("Optional", x, y, self)
892            .set_all(self.attributes.iter())
893            .add(v)
894            .add(i)
895    }
896}
897
898/// A vertical group of elements, drawn from top to bottom.
899///
900/// Also see `Sequence` for a horizontal group of elements.
901#[derive(Debug, Clone)]
902pub struct Stack<N> {
903    children: Vec<N>,
904    left_padding: i64,
905    right_padding: i64,
906    spacing: i64,
907    attributes: HashMap<String, String>,
908}
909
910impl<N> Stack<N> {
911    #[must_use]
912    pub fn new(children: Vec<N>) -> Self {
913        let mut s = Self {
914            children,
915            ..Self::default()
916        };
917        s.attributes.insert("class".to_owned(), "stack".to_owned());
918        s
919    }
920
921    pub fn push(&mut self, child: N) {
922        self.children.push(child);
923    }
924
925    #[must_use]
926    pub fn into_inner(self) -> Vec<N> {
927        self.children
928    }
929
930    /// Access an attribute on the main SVG-element that will be drawn.
931    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
932        self.attributes.entry(key)
933    }
934
935    fn padded_height(&self, child: &dyn Node, next_child: &dyn Node) -> i64 {
936        child.entry_height()
937            + cmp::max(child.height_below_entry() + self.spacing, ARC_RADIUS * 2)
938            + ARC_RADIUS
939            + cmp::max(0, ARC_RADIUS - next_child.entry_height())
940    }
941
942    fn left_padding(&self) -> i64 {
943        if self.children.len() > 1 {
944            cmp::max(self.left_padding, ARC_RADIUS)
945        } else {
946            0
947        }
948    }
949
950    fn right_padding(&self) -> i64 {
951        if self.children.len() > 1 {
952            cmp::max(self.right_padding, ARC_RADIUS * 2)
953        } else {
954            0
955        }
956    }
957}
958
959impl<N> Default for Stack<N> {
960    fn default() -> Self {
961        Self {
962            children: Vec::default(),
963            left_padding: 10,
964            right_padding: 10,
965            spacing: ARC_RADIUS,
966            attributes: HashMap::default(),
967        }
968    }
969}
970
971impl<N> iter::FromIterator<N> for Stack<N> {
972    fn from_iter<T: IntoIterator<Item = N>>(iter: T) -> Self {
973        Self::new(iter.into_iter().collect())
974    }
975}
976
977impl<N> Node for Stack<N>
978where
979    N: Node,
980{
981    fn entry_height(&self) -> i64 {
982        self.children
983            .first()
984            .map(Node::entry_height)
985            .unwrap_or_default()
986    }
987
988    fn height(&self) -> i64 {
989        self.children
990            .iter()
991            .zip(self.children.iter().skip(1))
992            .map(|(c, nc)| self.padded_height(c, nc))
993            .sum::<i64>()
994            + self.children.last().map(Node::height).unwrap_or_default()
995    }
996
997    fn width(&self) -> i64 {
998        let l = self.left_padding() + self.children.iter().max_width() + self.right_padding();
999        // If the final upwards connector touches the downward ones, add some space
1000        if self
1001            .children
1002            .iter()
1003            .rev()
1004            .skip(1)
1005            .rev()
1006            .any(|c| c.width() >= self.children.last().map(Node::width).unwrap_or_default())
1007        {
1008            l + ARC_RADIUS
1009        } else {
1010            l
1011        }
1012    }
1013
1014    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
1015        let mut g = svg::Element::new("g").set_all(self.attributes.iter()).add(
1016            svg::PathData::new(h_dir)
1017                .move_to(x, y + self.entry_height())
1018                .horizontal(self.left_padding())
1019                .into_path(),
1020        );
1021
1022        // Draw all the children but the last
1023        let mut running_y = y;
1024        for (child, next_child) in self.children.iter().zip(self.children.iter().skip(1)) {
1025            g.push(
1026                svg::PathData::new(h_dir)
1027                    .move_to(
1028                        x + self.left_padding() + child.width(),
1029                        running_y + child.entry_height(),
1030                    )
1031                    .arc(ARC_RADIUS, svg::Arc::WestToSouth)
1032                    .vertical(cmp::max(
1033                        0,
1034                        child.height_below_entry() + self.spacing - ARC_RADIUS * 2,
1035                    ))
1036                    .arc(ARC_RADIUS, svg::Arc::NorthToWest)
1037                    .horizontal(-child.width())
1038                    .arc(ARC_RADIUS, svg::Arc::EastToSouth)
1039                    .vertical(cmp::max(0, next_child.entry_height() - ARC_RADIUS))
1040                    .vertical(cmp::max(
1041                        0,
1042                        (self.spacing - ARC_RADIUS * 2) / 2 + (self.spacing - ARC_RADIUS * 2) % 2,
1043                    ))
1044                    .arc(ARC_RADIUS, svg::Arc::NorthToEast)
1045                    .horizontal(self.left_padding() - ARC_RADIUS)
1046                    .into_path(),
1047            );
1048            g.push(child.draw(x + self.left_padding(), running_y, h_dir));
1049            running_y += self.padded_height(child, next_child);
1050        }
1051
1052        // Draw the last (possibly only) child and its connectors
1053        if let Some(child) = self.children.last() {
1054            if self.children.len() > 1 {
1055                g.push(
1056                    svg::PathData::new(h_dir)
1057                        .move_to(
1058                            x + self.left_padding() + child.width(),
1059                            running_y + child.entry_height(),
1060                        )
1061                        .horizontal(
1062                            self.width() - child.width() - self.left_padding() - ARC_RADIUS * 2,
1063                        )
1064                        .arc(ARC_RADIUS, svg::Arc::WestToNorth)
1065                        .vertical(
1066                            -self.height()
1067                                + child.height_below_entry()
1068                                + ARC_RADIUS * 2
1069                                + self.entry_height(),
1070                        )
1071                        .arc(ARC_RADIUS, svg::Arc::SouthToEast)
1072                        .into_path(),
1073                );
1074            }
1075            g.push(child.draw(x + self.left_padding(), running_y, h_dir));
1076        }
1077
1078        g.debug("Stack", x, y, self)
1079    }
1080}
1081
1082/// A container of elements, drawn vertically, where exactly one element has to be picked
1083///
1084/// Use `Empty` as one of the elements to make the entire `Choice` optional (a shorthand for
1085/// `Optional(Choice(..))`.
1086#[derive(Debug, Clone)]
1087pub struct Choice<N> {
1088    children: Vec<N>,
1089    spacing: i64,
1090    attributes: HashMap<String, String>,
1091}
1092
1093impl<N> Choice<N> {
1094    #[must_use]
1095    pub fn new(children: Vec<N>) -> Self {
1096        let mut c = Self {
1097            children,
1098            ..Self::default()
1099        };
1100        c.attributes.insert("class".to_owned(), "choice".to_owned());
1101        c
1102    }
1103
1104    pub fn push(&mut self, child: N) {
1105        self.children.push(child);
1106    }
1107
1108    /// Access an attribute on the main SVG-element that will be drawn.
1109    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
1110        self.attributes.entry(key)
1111    }
1112
1113    #[must_use]
1114    pub fn into_inner(self) -> Vec<N> {
1115        self.children
1116    }
1117
1118    fn inner_padding(&self) -> i64 {
1119        if self.children.len() > 1 {
1120            ARC_RADIUS * 2
1121        } else {
1122            0
1123        }
1124    }
1125
1126    fn padded_height(&self, child: &dyn Node) -> i64 {
1127        cmp::max(ARC_RADIUS, child.entry_height()) + child.height_below_entry() + self.spacing
1128    }
1129}
1130
1131impl<N> iter::FromIterator<N> for Choice<N> {
1132    fn from_iter<T: IntoIterator<Item = N>>(iter: T) -> Self {
1133        Self::new(iter.into_iter().collect())
1134    }
1135}
1136
1137impl<N> Default for Choice<N> {
1138    fn default() -> Self {
1139        Self {
1140            children: Vec::default(),
1141            spacing: 10,
1142            attributes: HashMap::default(),
1143        }
1144    }
1145}
1146
1147impl<N> Node for Choice<N>
1148where
1149    N: Node,
1150{
1151    fn entry_height(&self) -> i64 {
1152        self.children
1153            .first()
1154            .map(Node::entry_height)
1155            .unwrap_or_default()
1156    }
1157
1158    fn height(&self) -> i64 {
1159        if self.children.is_empty() {
1160            0
1161        } else if self.children.len() == 1 {
1162            self.children.iter().total_height()
1163        } else {
1164            self.entry_height()
1165                + cmp::max(
1166                    ARC_RADIUS,
1167                    self.spacing + self.children[0].height_below_entry(),
1168                )
1169                + self
1170                    .children
1171                    .iter()
1172                    .skip(1)
1173                    .map(|c| self.padded_height(c))
1174                    .sum::<i64>()
1175                - self.spacing
1176        }
1177    }
1178
1179    fn width(&self) -> i64 {
1180        if self.children.len() > 1 {
1181            self.inner_padding() + self.children.iter().max_width() + self.inner_padding()
1182        } else {
1183            self.children.iter().max_width()
1184        }
1185    }
1186
1187    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
1188        let mut g = svg::Element::new("g").set_all(self.attributes.iter());
1189
1190        // The top, horizontal connectors
1191        g.push(
1192            svg::PathData::new(h_dir)
1193                .move_to(x, y + self.entry_height())
1194                .horizontal(self.inner_padding())
1195                .move_rel(
1196                    self.children.first().map(Node::width).unwrap_or_default(),
1197                    0,
1198                )
1199                .horizontal(
1200                    self.width()
1201                        - self.inner_padding()
1202                        - self.children.first().map(Node::width).unwrap_or_default(),
1203                )
1204                .into_path(),
1205        );
1206
1207        // The first child is simply drawn in-line
1208        if let Some(child) = self.children.first() {
1209            g.push(child.draw(x + self.inner_padding(), y, h_dir));
1210        }
1211
1212        // If there are more children, we draw all kinds of things
1213        if self.children.len() > 1 {
1214            // The downward arcs
1215            g.push(
1216                svg::PathData::new(h_dir)
1217                    .move_to(x, y + self.entry_height())
1218                    .arc(ARC_RADIUS, svg::Arc::WestToSouth)
1219                    .vertical(cmp::max(
1220                        0,
1221                        self.children[0].height_below_entry() + self.spacing - ARC_RADIUS,
1222                    ))
1223                    .move_rel(self.width() - ARC_RADIUS * 2, 0)
1224                    .vertical(-cmp::max(
1225                        0,
1226                        self.children[0].height_below_entry() + self.spacing - ARC_RADIUS,
1227                    ))
1228                    .arc(ARC_RADIUS, svg::Arc::SouthToEast)
1229                    .into_path(),
1230            );
1231
1232            // The downward connectors, drawn individually
1233            let mut running_y = y
1234                + self.entry_height()
1235                + cmp::max(
1236                    ARC_RADIUS,
1237                    self.spacing + self.children[0].height_below_entry(),
1238                );
1239            for child in self.children.iter().skip(1).rev().skip(1).rev() {
1240                let z = self.padded_height(child);
1241                let zz = cmp::max(0, child.entry_height() - ARC_RADIUS);
1242                let z = z - zz;
1243                g.push(
1244                    svg::PathData::new(h_dir)
1245                        .move_to(x + ARC_RADIUS, running_y + zz)
1246                        .vertical(z)
1247                        .move_rel(self.width() - ARC_RADIUS * 2, 0)
1248                        .vertical(-z)
1249                        .into_path(),
1250                );
1251                running_y += z + zz;
1252            }
1253
1254            // The children and arcs around them
1255            let mut running_y = y
1256                + self.entry_height()
1257                + cmp::max(
1258                    ARC_RADIUS,
1259                    self.spacing + self.children[0].height_below_entry(),
1260                );
1261            for child in self.children.iter().skip(1) {
1262                g.push(
1263                    svg::PathData::new(h_dir)
1264                        .move_to(x + ARC_RADIUS, running_y)
1265                        .vertical(cmp::max(0, child.entry_height() - ARC_RADIUS))
1266                        .arc(ARC_RADIUS, svg::Arc::NorthToEast)
1267                        .move_rel(child.width(), 0)
1268                        .horizontal(self.children.iter().max_width() - child.width())
1269                        .arc(ARC_RADIUS, svg::Arc::WestToNorth)
1270                        .vertical(-cmp::max(0, child.entry_height() - ARC_RADIUS))
1271                        .into_path(),
1272                );
1273                g.push(child.draw(
1274                    x + ARC_RADIUS * 2,
1275                    running_y + cmp::max(0, ARC_RADIUS - child.entry_height()),
1276                    h_dir,
1277                ));
1278                running_y += self.padded_height(child);
1279            }
1280        }
1281
1282        g.debug("Choice", x, y, self)
1283    }
1284}
1285
1286/// Wraps one element by providing a backwards-path through another element.
1287#[derive(Debug, Clone)]
1288pub struct Repeat<I, R> {
1289    inner: I,
1290    repeat: R,
1291    spacing: i64,
1292    attributes: HashMap<String, String>,
1293}
1294
1295impl<I, R> Repeat<I, R> {
1296    pub fn new(inner: I, repeat: R) -> Self {
1297        let mut r = Self {
1298            inner,
1299            repeat,
1300            spacing: 10,
1301            attributes: HashMap::default(),
1302        };
1303        r.attributes.insert("class".to_owned(), "repeat".to_owned());
1304        r
1305    }
1306
1307    /// Access an attribute on the main SVG-element that will be drawn.
1308    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
1309        self.attributes.entry(key)
1310    }
1311}
1312
1313impl<I, R> Repeat<I, R>
1314where
1315    I: Node,
1316    R: Node,
1317{
1318    fn height_between_entries(&self) -> i64 {
1319        cmp::max(
1320            ARC_RADIUS * 2,
1321            self.inner.height_below_entry() + self.spacing + self.repeat.entry_height(),
1322        )
1323    }
1324}
1325
1326impl<I, R> Default for Repeat<I, R>
1327where
1328    I: Default,
1329    R: Default,
1330{
1331    fn default() -> Self {
1332        Self {
1333            inner: Default::default(),
1334            repeat: Default::default(),
1335            spacing: 10,
1336            attributes: HashMap::default(),
1337        }
1338    }
1339}
1340
1341impl<I, R> Node for Repeat<I, R>
1342where
1343    I: Node,
1344    R: Node,
1345{
1346    fn entry_height(&self) -> i64 {
1347        self.inner.entry_height()
1348    }
1349
1350    fn height(&self) -> i64 {
1351        self.inner.entry_height() + self.height_between_entries() + self.repeat.height_below_entry()
1352    }
1353
1354    fn width(&self) -> i64 {
1355        ARC_RADIUS + cmp::max(self.repeat.width(), self.inner.width()) + ARC_RADIUS
1356    }
1357
1358    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
1359        let mut g = svg::Element::new("g").set_all(self.attributes.iter());
1360
1361        g.push(
1362            svg::PathData::new(h_dir)
1363                .move_to(x, y + self.entry_height())
1364                .horizontal(ARC_RADIUS)
1365                .move_rel(self.inner.width(), 0)
1366                .horizontal(cmp::max(
1367                    ARC_RADIUS,
1368                    self.repeat.width() - self.inner.width() + ARC_RADIUS,
1369                ))
1370                .move_rel(-ARC_RADIUS, 0)
1371                .arc(ARC_RADIUS, svg::Arc::WestToSouth)
1372                .vertical(self.height_between_entries() - ARC_RADIUS * 2)
1373                .arc(ARC_RADIUS, svg::Arc::NorthToWest)
1374                .move_rel(-self.repeat.width(), 0)
1375                .horizontal(cmp::min(0, self.repeat.width() - self.inner.width()))
1376                .arc(ARC_RADIUS, svg::Arc::EastToNorth)
1377                .vertical(-self.height_between_entries() + ARC_RADIUS * 2)
1378                .arc(ARC_RADIUS, svg::Arc::SouthToEast)
1379                .into_path(),
1380        )
1381        .push(self.repeat.draw(
1382            x + self.width() - self.repeat.width() - ARC_RADIUS,
1383            y + self.height() - self.repeat.height_below_entry() - self.repeat.entry_height(),
1384            h_dir.invert(),
1385        ));
1386        g.push(self.inner.draw(x + ARC_RADIUS, y, h_dir));
1387        g.debug("Repeat", x, y, self)
1388    }
1389}
1390
1391/// A rectangle drawn with the given dimensions, used for visual debugging
1392#[derive(Debug)]
1393#[doc(hidden)]
1394pub struct Debug {
1395    entry_height: i64,
1396    height: i64,
1397    width: i64,
1398    attributes: HashMap<String, String>,
1399}
1400
1401impl Debug {
1402    #[must_use]
1403    /// # Panics
1404    /// If `entry_height` is not smaller than `height`
1405    pub fn new(entry_height: i64, height: i64, width: i64) -> Self {
1406        assert!(entry_height < height);
1407        let mut d = Self {
1408            entry_height,
1409            height,
1410            width,
1411            attributes: HashMap::default(),
1412        };
1413
1414        d.attributes.insert("class".to_owned(), "debug".to_owned());
1415        d.attributes.insert(
1416            "style".to_owned(),
1417            "fill: hsla(0, 100%, 90%, 0.9); stroke-width: 2; stroke: red".to_owned(),
1418        );
1419        d
1420    }
1421}
1422
1423impl Node for Debug {
1424    fn entry_height(&self) -> i64 {
1425        self.entry_height
1426    }
1427    fn height(&self) -> i64 {
1428        self.height
1429    }
1430    fn width(&self) -> i64 {
1431        self.width
1432    }
1433
1434    fn draw(&self, x: i64, y: i64, _: HDir) -> svg::Element {
1435        svg::Element::new("rect")
1436            .set("x", &x)
1437            .set("y", &y)
1438            .set("height", &self.height())
1439            .set("width", &self.width())
1440            .set_all(self.attributes.iter())
1441            .debug("Debug", x, y, self)
1442    }
1443}
1444
1445/// A dummy-element which has no size and draws nothing.
1446///
1447/// This can be used in conjunction with `Choice` (to indicate that one of the options
1448/// is blank, a shorthand for an `Optional(Choice)`), `Repeat` (if there are
1449/// zero-or-more repetitions or if there is no joining element), or `LabeledBox`
1450/// (if the label should be empty).
1451#[derive(Debug, Clone, Default)]
1452pub struct Empty;
1453
1454impl Node for Empty {
1455    fn entry_height(&self) -> i64 {
1456        0
1457    }
1458    fn height(&self) -> i64 {
1459        0
1460    }
1461    fn width(&self) -> i64 {
1462        0
1463    }
1464
1465    fn draw(&self, x: i64, y: i64, _: HDir) -> svg::Element {
1466        svg::Element::new("g").debug("Empty", x, y, self)
1467    }
1468}
1469
1470/// A box drawn around the given element and a label placed inside the box, above the element.
1471///
1472/// You may want to use `Comment` or `Empty` for the label.
1473#[derive(Debug, Clone)]
1474pub struct LabeledBox<T, U> {
1475    inner: T,
1476    label: U,
1477    spacing: i64,
1478    padding: i64,
1479    attributes: HashMap<String, String>,
1480}
1481
1482impl<T> LabeledBox<T, Empty> {
1483    /// Construct a box with a label set to `Empty`
1484    pub fn without_label(inner: T) -> Self {
1485        Self::new(inner, Empty)
1486    }
1487}
1488
1489impl<T, U> LabeledBox<T, U> {
1490    pub fn new(inner: T, label: U) -> Self {
1491        let mut l = Self {
1492            inner,
1493            label,
1494            spacing: 8,
1495            padding: 8,
1496            attributes: HashMap::default(),
1497        };
1498        l.attributes
1499            .insert("class".to_owned(), "labeledbox".to_owned());
1500        l
1501    }
1502
1503    /// Access an attribute on the main SVG-element that will be drawn.
1504    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
1505        self.attributes.entry(key)
1506    }
1507}
1508
1509impl<T, U> Default for LabeledBox<T, U>
1510where
1511    T: Default,
1512    U: Default,
1513{
1514    fn default() -> Self {
1515        Self {
1516            inner: Default::default(),
1517            label: Default::default(),
1518            spacing: 8,
1519            padding: 8,
1520            attributes: HashMap::default(),
1521        }
1522    }
1523}
1524
1525impl<T, U> LabeledBox<T, U>
1526where
1527    T: Node,
1528    U: Node,
1529{
1530    fn spacing(&self) -> i64 {
1531        if self.label.height() > 0 {
1532            self.spacing
1533        } else {
1534            0
1535        }
1536    }
1537
1538    fn padding(&self) -> i64 {
1539        if self.label.height() + self.inner.height() + self.label.width() + self.inner.width() > 0 {
1540            self.padding
1541        } else {
1542            0
1543        }
1544    }
1545}
1546
1547impl<T, U> Node for LabeledBox<T, U>
1548where
1549    T: Node,
1550    U: Node,
1551{
1552    fn entry_height(&self) -> i64 {
1553        self.padding() + self.label.height() + self.spacing() + self.inner.entry_height()
1554    }
1555
1556    fn height(&self) -> i64 {
1557        self.padding() + self.label.height() + self.spacing() + self.inner.height() + self.padding()
1558    }
1559
1560    fn width(&self) -> i64 {
1561        self.padding() + cmp::max(self.inner.width(), self.label.width()) + self.padding()
1562    }
1563
1564    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
1565        svg::Element::new("g")
1566            .add(
1567                svg::Element::new("rect")
1568                    .set("x", &x)
1569                    .set("y", &y)
1570                    .set("height", &self.height())
1571                    .set("width", &self.width()),
1572            )
1573            .add(
1574                svg::PathData::new(h_dir)
1575                    .move_to(x, y + self.entry_height())
1576                    .horizontal(self.padding())
1577                    .move_rel(self.inner.width(), 0)
1578                    .horizontal(self.width() - self.inner.width() - self.padding())
1579                    .into_path(),
1580            )
1581            .add(
1582                self.label
1583                    .draw(x + self.padding(), y + self.padding(), h_dir),
1584            )
1585            .add(self.inner.draw(
1586                x + self.padding(),
1587                y + self.padding() + self.label.height() + self.spacing(),
1588                h_dir,
1589            ))
1590            .set_all(self.attributes.iter())
1591            .debug("LabeledBox", x, y, self)
1592    }
1593}
1594
1595/// A label / verbatim text, drawn in-line
1596#[derive(Debug, Clone)]
1597pub struct Comment {
1598    text: String,
1599    attributes: HashMap<String, String>,
1600}
1601
1602impl Comment {
1603    #[must_use]
1604    pub fn new(text: String) -> Self {
1605        let mut c = Self {
1606            text,
1607            attributes: HashMap::default(),
1608        };
1609        c.attributes
1610            .insert("class".to_owned(), "comment".to_owned());
1611        c
1612    }
1613
1614    /// Access an attribute on the main SVG-element that will be drawn.
1615    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
1616        self.attributes.entry(key)
1617    }
1618}
1619
1620impl Node for Comment {
1621    fn entry_height(&self) -> i64 {
1622        10
1623    }
1624    fn height(&self) -> i64 {
1625        20
1626    }
1627    fn width(&self) -> i64 {
1628        i64::try_from(text_width(&self.text)).unwrap() * 7 + 10
1629    }
1630
1631    fn draw(&self, x: i64, y: i64, _: HDir) -> svg::Element {
1632        svg::Element::new("text")
1633            .set_all(self.attributes.iter())
1634            .set("x", &(x + self.width() / 2))
1635            .set("y", &(y + 15))
1636            .text(&self.text)
1637            .debug("Comment", x, y, self)
1638    }
1639}
1640
1641#[derive(Debug, Clone)]
1642pub struct Diagram<N> {
1643    root: N,
1644    extra_attributes: HashMap<String, String>,
1645    extra_elements: Vec<svg::Element>,
1646    left_padding: i64,
1647    right_padding: i64,
1648    top_padding: i64,
1649    bottom_padding: i64,
1650}
1651
1652impl<N: Node> Diagram<N> {
1653    /// Create a diagram using the given root-element.
1654    ///
1655    /// ```
1656    /// use railroad::*;
1657    ///
1658    /// let mut seq: Sequence::<Box<dyn Node>> = Sequence::default();
1659    /// seq.push(Box::new(Start))
1660    ///    .push(Box::new(Terminal::new("Foobar".to_owned())))
1661    ///    .push(Box::new(End));
1662    ///
1663    /// let mut dia = Diagram::new(seq);
1664    /// println!("{}", dia);
1665    /// ```
1666    pub fn new(root: N) -> Self {
1667        Self {
1668            root,
1669            extra_attributes: HashMap::default(),
1670            extra_elements: Vec::default(),
1671            left_padding: 10,
1672            right_padding: 10,
1673            top_padding: 10,
1674            bottom_padding: 10,
1675        }
1676    }
1677
1678    /// Create a diagram using the given root-element, adding the given stylesheet.
1679    ///
1680    /// ```rust
1681    /// use railroad::*;
1682    ///
1683    /// let mut seq: Sequence::<Box<dyn Node>> = Sequence::default();
1684    /// seq.push(Box::new(Start))
1685    ///    .push(Box::new(Terminal::new("Foobar".to_owned())))
1686    ///    .push(Box::new(End));
1687    ///
1688    /// let dia = Diagram::new_with_stylesheet(seq, &Stylesheet::Light);
1689    /// println!("{}", dia);
1690    /// ```
1691    pub fn new_with_stylesheet(root: N, style: &Stylesheet) -> Self {
1692        let mut dia = Self::new(root);
1693        dia.add_stylesheet(style);
1694        dia
1695    }
1696
1697    /// Create a diagram which has this library's default CSS style included.
1698    pub fn with_default_css(root: N) -> Self {
1699        let mut dia = Self::new(root);
1700        dia.add_default_css();
1701        dia
1702    }
1703
1704    pub fn add_stylesheet(&mut self, style: &Stylesheet) {
1705        self.add_css(style.stylesheet());
1706    }
1707
1708    /// Add the default CSS as an additional `<style>` element.
1709    pub fn add_default_css(&mut self) {
1710        self.add_css(DEFAULT_CSS);
1711    }
1712
1713    /// Add the given CSS as an additional `<style>` element.
1714    pub fn add_css(&mut self, css: &str) {
1715        self.add_element(
1716            svg::Element::new("style")
1717                .set("type", "text/css")
1718                .raw_text(css),
1719        );
1720    }
1721
1722    /// Set an attribute on the `<svg>`-tag.
1723    pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
1724        self.extra_attributes.entry(key)
1725    }
1726
1727    /// Add an additional `svg::Element` which is written before the root-element
1728    pub fn add_element(&mut self, e: svg::Element) -> &mut Self {
1729        self.extra_elements.push(e);
1730        self
1731    }
1732
1733    /// Write this diagram's SVG-code to the given writer.
1734    ///
1735    /// # Errors
1736    /// Returns errors in the underlying writer.
1737    pub fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
1738        write!(writer, "{}", self.draw(0, 0, HDir::LTR))
1739    }
1740
1741    /// Return the root-element this diagram's root element
1742    pub fn into_inner(self) -> N {
1743        self.root
1744    }
1745}
1746
1747impl<N> Default for Diagram<N>
1748where
1749    N: Default,
1750{
1751    fn default() -> Self {
1752        Self {
1753            root: Default::default(),
1754            extra_attributes: HashMap::default(),
1755            extra_elements: Vec::default(),
1756            left_padding: 10,
1757            right_padding: 10,
1758            top_padding: 10,
1759            bottom_padding: 10,
1760        }
1761    }
1762}
1763
1764impl<N> Node for Diagram<N>
1765where
1766    N: Node,
1767{
1768    fn entry_height(&self) -> i64 {
1769        0
1770    }
1771
1772    fn height(&self) -> i64 {
1773        self.top_padding + self.root.height() + self.bottom_padding
1774    }
1775
1776    fn width(&self) -> i64 {
1777        self.left_padding + self.root.width() + self.right_padding
1778    }
1779
1780    fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
1781        let mut e = svg::Element::new("svg")
1782            .set("xmlns", "http://www.w3.org/2000/svg")
1783            .set("xmlns:xlink", "http://www.w3.org/1999/xlink")
1784            .set("class", "railroad")
1785            .set(
1786                "viewBox",
1787                &format!("0 0 {} {}", self.width(), self.height()),
1788            );
1789        for (k, v) in &self.extra_attributes {
1790            e = e.set(&k, &v);
1791        }
1792        for extra_ele in self.extra_elements.iter().cloned() {
1793            e = e.add(extra_ele);
1794        }
1795        e.add(
1796            svg::Element::new("rect")
1797                .set("width", "100%")
1798                .set("height", "100%")
1799                .set("class", "railroad_canvas"),
1800        )
1801        .add(
1802            self.root
1803                .draw(x + self.left_padding, y + self.top_padding, h_dir),
1804        )
1805    }
1806}
1807
1808impl<N> fmt::Display for Diagram<N>
1809where
1810    N: Node,
1811{
1812    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1813        write!(f, "{}", self.draw(0, 0, HDir::LTR))
1814    }
1815}
1816
1817#[cfg(test)]
1818mod tests {
1819    use super::*;
1820
1821    #[test]
1822    fn debug_impl() {
1823        let s = Sequence::new(vec![
1824            Box::new(SimpleStart) as Box<dyn Node>,
1825            Box::new(SimpleEnd),
1826        ]);
1827        assert_eq!(
1828            "Sequence { children: [Node { entry_height: 5, height: 10, width: 15 }, Node { entry_height: 5, height: 10, width: 15 }], spacing: 10 }",
1829            format!("{:?}", &s)
1830        );
1831        assert_eq!(
1832            "Node { entry_height: 5, height: 10, width: 40 }",
1833            format!("{:?}", &s as &dyn Node)
1834        );
1835    }
1836}