use crate::fst::Fst;
use crate::semiring::Semiring;
use std::fmt::Write;
#[derive(Debug, Clone)]
pub struct DrawingConfig {
pub show_weights: bool,
pub show_state_ids: bool,
pub show_final_weights: bool,
pub use_symbols: bool,
pub horizontal: bool,
pub node_shape: String,
pub node_color: String,
pub start_color: String,
pub final_color: String,
}
impl Default for DrawingConfig {
fn default() -> Self {
Self {
show_weights: true,
show_state_ids: true,
show_final_weights: true,
use_symbols: true,
horizontal: true,
node_shape: "circle".to_string(),
node_color: "white".to_string(),
start_color: "lightblue".to_string(),
final_color: "lightgreen".to_string(),
}
}
}
pub fn draw_fst<W, F>(
fst: &F,
config: DrawingConfig,
input_symbols: Option<&crate::utils::SymbolTable>,
output_symbols: Option<&crate::utils::SymbolTable>,
) -> Result<String, std::fmt::Error>
where
W: Semiring + std::fmt::Display,
F: Fst<W>,
{
let mut output = String::new();
writeln!(output, "digraph FST {{")?;
if config.horizontal {
writeln!(output, " rankdir=LR;")?;
}
writeln!(output, " size=\"8,5\";")?;
writeln!(output, " node [shape={}];", config.node_shape)?;
for state in fst.states() {
let mut label = if config.show_state_ids {
format!("{}", state)
} else {
String::new()
};
let is_final = fst.is_final(state);
let is_start = fst.start() == Some(state);
if is_final && config.show_final_weights {
if let Some(weight) = fst.final_weight(state) {
if !label.is_empty() {
label.push_str("\\n");
}
write!(label, "{}", weight)?;
}
}
let mut style = format!("fillcolor={}", config.node_color);
if is_start {
style = format!("fillcolor={}", config.start_color);
} else if is_final {
style = format!("fillcolor={}", config.final_color);
}
writeln!(
output,
" {} [label=\"{}\" style=filled {}];",
state, label, style
)?;
}
for state in fst.states() {
for arc in fst.arcs(state) {
let ilabel_str = if config.use_symbols {
if let Some(symbols) = input_symbols {
symbols
.find(arc.ilabel)
.unwrap_or(&format!("{}", arc.ilabel))
.to_string()
} else {
format!("{}", arc.ilabel)
}
} else {
format!("{}", arc.ilabel)
};
let olabel_str = if config.use_symbols {
if let Some(symbols) = output_symbols {
symbols
.find(arc.olabel)
.unwrap_or(&format!("{}", arc.olabel))
.to_string()
} else {
format!("{}", arc.olabel)
}
} else {
format!("{}", arc.olabel)
};
let arc_label = if ilabel_str == olabel_str {
if config.show_weights {
format!("{} / {}", ilabel_str, arc.weight)
} else {
ilabel_str
}
} else if config.show_weights {
format!("{}:{} / {}", ilabel_str, olabel_str, arc.weight)
} else {
format!("{}:{}", ilabel_str, olabel_str)
};
writeln!(
output,
" {} -> {} [label=\"{}\"];",
state, arc.nextstate, arc_label
)?;
}
}
writeln!(output, "}}")?;
Ok(output)
}
pub fn draw_fst_default<W, F>(fst: &F) -> Result<String, std::fmt::Error>
where
W: Semiring + std::fmt::Display,
F: Fst<W>,
{
draw_fst(fst, DrawingConfig::default(), None, None)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn test_drawing_config_default() {
let config = DrawingConfig::default();
assert!(config.show_weights);
assert!(config.show_state_ids);
assert!(config.horizontal);
assert_eq!(config.node_shape, "circle");
}
#[test]
fn test_draw_fst_default() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
fst.set_start(s0);
fst.set_final(s1, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::one(), s1));
let dot = draw_fst_default(&fst).unwrap();
assert!(dot.contains("digraph FST"));
assert!(dot.contains("rankdir=LR"));
}
#[test]
fn test_draw_fst_with_config() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
fst.set_start(s0);
fst.set_final(s0, TropicalWeight::one());
let config = DrawingConfig {
horizontal: false,
node_shape: "box".to_string(),
..Default::default()
};
let dot = draw_fst(&fst, config, None, None).unwrap();
assert!(dot.contains("digraph FST"));
assert!(!dot.contains("rankdir=LR"));
assert!(dot.contains("shape=box"));
}
#[test]
fn test_draw_fst_with_symbols() {
let mut fst = VectorFst::<TropicalWeight>::new();
let mut symbols = SymbolTable::new();
let hello_id = symbols.add_symbol("hello");
let s0 = fst.add_state();
let s1 = fst.add_state();
fst.set_start(s0);
fst.set_final(s1, TropicalWeight::one());
fst.add_arc(s0, Arc::new(hello_id, hello_id, TropicalWeight::one(), s1));
let config = DrawingConfig {
use_symbols: true,
..Default::default()
};
let dot = draw_fst(&fst, config, Some(&symbols), Some(&symbols)).unwrap();
assert!(dot.contains("hello"));
}
}