pub fn escape_latex(text: String) -> String {
return text
.chars()
.map(|c| match c {
'\\' => r"{\textbackslash}".to_string(),
'&' => r"\&".to_string(),
'%' => r"\%".to_string(),
'$' => r"\$".to_string(),
'#' => r"\#".to_string(),
'_' => r"\_".to_string(),
'{' => r"\{".to_string(),
'}' => r"\}".to_string(),
'~' => r"{\textasciitilde}".to_string(),
'^' => r"{\textasciicircum}".to_string(),
c => c.to_string(),
})
.collect();
}
pub struct LatexGraphTransition {
pub(crate) to: usize,
pub(crate) label: String,
}
pub struct LatexGraphState {
pub(crate) label: String,
pub(crate) transitions: Vec<LatexGraphTransition>,
pub(crate) initial: bool,
pub(crate) accept: bool,
}
pub struct LatexGraph {
pub(crate) states: Vec<LatexGraphState>,
}
impl LatexGraph {
pub fn to_tikz(&self, include_doc: bool) -> String {
let mut text_parts: Vec<String> = Vec::new();
if include_doc {
text_parts.push(
"\\documentclass{standalone}\n\\usepackage{tikz}\n\\usetikzlibrary{automata, positioning}\n\\begin{document}\n"
.into(),
);
}
text_parts.push("\\begin{tikzpicture}[node distance=2cm, auto]\n".into());
let mut transition_parts = Vec::new();
for (i, state) in self.states.iter().enumerate() {
let mut modifiers = String::new();
if state.initial {
modifiers += ", initial";
}
if state.accept {
modifiers += ", accepting"
}
if i == 0 {
text_parts.push(format!("\\node[state{modifiers}](q0){{$q_0$}};\n"));
} else {
text_parts.push(format!(
"\\node[state{modifiers}, right of=q{}](q{i}){{$q_{{{i}}}$}};\n",
i - 1,
));
}
for LatexGraphTransition { to, label } in &state.transitions {
let bend = match to.cmp(&i) {
std::cmp::Ordering::Less => "[bend left] ",
std::cmp::Ordering::Equal => "[loop below]",
std::cmp::Ordering::Greater => "[bend left] ",
};
transition_parts.push(format!(
"\\path[->] (q{i}) edge {bend} node {{{label}}} (q{to});\n",
));
}
}
text_parts.extend_from_slice(&transition_parts);
text_parts.push("\\end{tikzpicture}\n".into());
if include_doc {
text_parts.push("\\end{document}\n".into());
}
return text_parts.into_iter().collect();
}
}