1use 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
71const ARC_RADIUS: i64 = 12;
74
75fn text_width(s: &str) -> usize {
79 use unicode_width::UnicodeWidthStr;
80 s.width() + (s.width() / 20)
82}
83
84#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
97#[non_exhaustive]
98pub enum Stylesheet {
99 #[default]
101 Light,
102 Dark,
103 LightRendersafe,
105 DarkRendersafe,
107}
108
109impl Stylesheet {
110 #[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 #[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 #[must_use]
130 pub const fn is_light(&self) -> bool {
131 matches!(self, Self::Light | Self::LightRendersafe)
132 }
133
134 #[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
146pub const DEFAULT_CSS: &str = Stylesheet::Light.stylesheet();
148
149pub trait Node {
159 fn entry_height(&self) -> i64;
164
165 fn height(&self) -> i64;
167
168 fn width(&self) -> i64;
170
171 fn height_below_entry(&self) -> i64 {
173 self.height() - self.entry_height()
174 }
175
176 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
217pub trait NodeCollection {
219 fn max_entry_height(self) -> i64;
221
222 fn max_height(self) -> i64;
224
225 fn max_height_below_entry(self) -> i64;
227
228 fn max_width(self) -> i64;
230
231 fn total_width(self) -> i64;
233
234 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#[derive(Debug, Default, Clone, Copy)]
282pub enum LinkTarget {
283 #[default]
284 Blank,
285 Parent,
286 Top,
287}
288
289#[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 pub fn set_target(&mut self, target: Option<LinkTarget>) {
312 self.target = target;
313 }
314
315 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#[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 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#[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 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#[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#[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#[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#[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#[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#[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 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#[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 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#[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 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#[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 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 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 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 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#[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 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 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 if let Some(child) = self.children.first() {
1209 g.push(child.draw(x + self.inner_padding(), y, h_dir));
1210 }
1211
1212 if self.children.len() > 1 {
1214 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 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 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#[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 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#[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 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#[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#[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 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 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#[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 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 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 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 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 pub fn add_default_css(&mut self) {
1710 self.add_css(DEFAULT_CSS);
1711 }
1712
1713 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 pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
1724 self.extra_attributes.entry(key)
1725 }
1726
1727 pub fn add_element(&mut self, e: svg::Element) -> &mut Self {
1729 self.extra_elements.push(e);
1730 self
1731 }
1732
1733 pub fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
1738 write!(writer, "{}", self.draw(0, 0, HDir::LTR))
1739 }
1740
1741 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}