ere_core/visualization/
latex_graph.rs

1pub fn escape_latex(text: String) -> String {
2    return text
3        .chars()
4        .map(|c| match c {
5            '\\' => r"{\textbackslash}".to_string(),
6            '&' => r"\&".to_string(),
7            '%' => r"\%".to_string(),
8            '$' => r"\$".to_string(),
9            '#' => r"\#".to_string(),
10            '_' => r"\_".to_string(),
11            '{' => r"\{".to_string(),
12            '}' => r"\}".to_string(),
13            '~' => r"{\textasciitilde}".to_string(),
14            '^' => r"{\textasciicircum}".to_string(),
15            c => c.to_string(),
16        })
17        .collect();
18}
19
20pub struct LatexGraphTransition {
21    pub(crate) to: usize,
22    /// The label is a valid latex-encoded string to be inserted at the label.
23    pub(crate) label: String,
24}
25
26pub struct LatexGraphState {
27    /// The label is a valid latex-encoded string to be inserted at the label.
28    pub(crate) label: String,
29    pub(crate) transitions: Vec<LatexGraphTransition>,
30    pub(crate) initial: bool,
31    pub(crate) accept: bool,
32}
33
34/// Used for tikz visualizations of NFA-like graphs
35pub struct LatexGraph {
36    pub(crate) states: Vec<LatexGraphState>,
37}
38impl LatexGraph {
39    /// Writes a LaTeX TikZ representation to visualize the graph.
40    ///
41    /// If `include_doc` is `true`, will include the headers.
42    /// Otherwise, you should include `\usepackage{tikz}` and `\usetikzlibrary{automata, positioning}`.
43    pub fn to_tikz(&self, include_doc: bool) -> String {
44        let mut text_parts: Vec<String> = Vec::new();
45        if include_doc {
46            text_parts.push(
47                "\\documentclass{standalone}\n\\usepackage{tikz}\n\\usetikzlibrary{automata, positioning}\n\\begin{document}\n"
48                .into(),
49            );
50        }
51        text_parts.push("\\begin{tikzpicture}[node distance=2cm, auto]\n".into());
52
53        let mut transition_parts = Vec::new();
54
55        for (i, state) in self.states.iter().enumerate() {
56            let mut modifiers = String::new();
57            if state.initial {
58                modifiers += ", initial";
59            }
60            if state.accept {
61                modifiers += ", accepting"
62            }
63            if i == 0 {
64                text_parts.push(format!("\\node[state{modifiers}](q0){{$q_0$}};\n"));
65            } else {
66                text_parts.push(format!(
67                    "\\node[state{modifiers}, right of=q{}](q{i}){{$q_{{{}}}$}};\n",
68                    i - 1,
69                    state.label
70                ));
71            }
72
73            for LatexGraphTransition { to, label } in &state.transitions {
74                let bend = match to.cmp(&i) {
75                    std::cmp::Ordering::Less => "[bend left] ",
76                    std::cmp::Ordering::Equal => "[loop below]",
77                    std::cmp::Ordering::Greater => "[bend left] ",
78                };
79                transition_parts.push(format!(
80                    "\\path[->] (q{i}) edge {bend} node {{{label}}} (q{to});\n",
81                ));
82            }
83        }
84        text_parts.extend_from_slice(&transition_parts);
85
86        text_parts.push("\\end{tikzpicture}\n".into());
87        if include_doc {
88            text_parts.push("\\end{document}\n".into());
89        }
90        return text_parts.into_iter().collect();
91    }
92}