ere_core/visualization/
latex_graph.rs1use crate::visualization::layout::{BuildLayout, DAGLayout};
2
3pub fn escape_latex(text: impl AsRef<str>) -> String {
4 return text
5 .as_ref()
6 .chars()
7 .map(|c| match c {
8 '\\' => r"{\textbackslash}".to_string(),
9 '&' => r"\&".to_string(),
10 '%' => r"\%".to_string(),
11 '$' => r"\$".to_string(),
12 '#' => r"\#".to_string(),
13 '_' => r"\_".to_string(),
14 '{' => r"\{".to_string(),
15 '}' => r"\}".to_string(),
16 '~' => r"{\textasciitilde}".to_string(),
17 '^' => r"{\textasciicircum}".to_string(),
18 c => c.to_string(),
19 })
20 .collect();
21}
22
23pub struct LatexGraphTransition {
24 pub(crate) to: usize,
25 pub(crate) label: String,
27}
28impl LatexGraphTransition {
29 pub fn display_in_line(&self, from: usize) -> String {
30 let label = &self.label;
31 let bend = match self.to.cmp(&from) {
32 std::cmp::Ordering::Less => "[bend left] ",
33 std::cmp::Ordering::Equal => "[loop below]",
34 std::cmp::Ordering::Greater => "[bend left] ",
35 };
36 format!(
37 "\\path[->] (q{from}) edge {bend} node {{{label}}} (q{});\n",
38 self.to
39 )
40 }
41 pub fn display_straight(&self, from: usize) -> String {
42 let label = &self.label;
43 format!(
44 "\\path[->] (q{from}) edge node {{{label}}} (q{});\n",
45 self.to
46 )
47 }
48}
49
50pub struct LatexGraphState {
51 pub(crate) label: String,
53 pub(crate) transitions: Vec<LatexGraphTransition>,
54 pub(crate) initial: bool,
55 pub(crate) accept: bool,
56}
57impl LatexGraphState {
58 pub fn display_in_line(&self, idx: usize) -> String {
60 let mut modifiers = String::new();
61 if self.initial {
62 modifiers += ", initial";
63 }
64 if self.accept {
65 modifiers += ", accepting"
66 }
67 let label = escape_latex(&self.label);
68 if idx == 0 {
69 return format!("\\node[state{modifiers}](q0){{{label}}};\n",);
70 } else {
71 return format!(
72 "\\node[state{modifiers}, right of=q{}](q{idx}){{{label}}};\n",
73 idx - 1,
74 );
75 }
76 }
77
78 pub fn display_at(&self, idx: usize, x: f64, y: f64) -> String {
79 let mut modifiers = String::new();
80 if self.initial {
81 modifiers += ", initial";
82 }
83 if self.accept {
84 modifiers += ", accepting"
85 }
86 let label = escape_latex(&self.label);
87 return format!("\\node[state{modifiers}](q{idx}) at ({x}, {y}) {{{label}}};\n",);
88 }
89}
90
91pub struct LatexGraph {
93 pub(crate) states: Vec<LatexGraphState>,
94}
95impl LatexGraph {
96 pub fn to_tikz(&self, include_doc: bool) -> String {
101 let layout = self.pick_layout().map(|layout| layout.layout());
102
103 let mut text_parts: Vec<String> = Vec::new();
104 if include_doc {
105 text_parts.push(
106 "\\documentclass{standalone}\n\\usepackage{tikz}\n\\usetikzlibrary{automata, positioning}\n\\begin{document}\n"
107 .into(),
108 );
109 }
110 text_parts.push("\\begin{tikzpicture}[node distance=2cm, auto]\n".into());
111
112 let mut transition_parts = Vec::new();
113
114 for (i, state) in self.states.iter().enumerate() {
115 if let Some(layout) = layout.as_ref() {
116 let (x, y) = layout[i];
117 text_parts.push(state.display_at(i, x * 2.0, y * 2.0));
118 } else {
119 text_parts.push(state.display_in_line(i));
120 }
121
122 for tr in &state.transitions {
123 if let Some(layout) = layout.as_ref() {
124 let x = layout[i].0;
125 let x_next = layout[tr.to].0;
126 if x_next - x > 1.0 {
127 transition_parts.push(tr.display_in_line(i));
128 } else {
129 transition_parts.push(tr.display_straight(i));
130 }
131 } else {
132 transition_parts.push(tr.display_in_line(i));
133 }
134 }
135 }
136 text_parts.extend_from_slice(&transition_parts);
137
138 text_parts.push("\\end{tikzpicture}\n".into());
139 if include_doc {
140 text_parts.push("\\end{document}\n".into());
141 }
142 return text_parts.into_iter().collect();
143 }
144
145 fn pick_layout<'a>(&'a self) -> Option<Box<dyn BuildLayout + 'a>> {
146 if let Some(dag) = DAGLayout::new(self) {
147 return Some(Box::new(dag));
148 }
149
150 return None;
152 }
153}