use std::io::ErrorKind; use std::{fs, io}; use svg::node::element::{Element, Group, Text}; use time_period::PDateTime; use crate::references::dates; pub mod period; pub mod range; use svg::node::element::{Line, Style}; use svg::Document; const HEIGHT: i64 = 32; const GAP: i64 = 5; const LINE_HEIGHT: i64 = HEIGHT + GAP * 2; const SECOND_PER_PX: i64 = 9000; const GAP_PER_CASE: i64 = 40; /// Represent a test suite in SVG fn get_style() -> String { return " text { font-size: 28px; font-family: roboto, sans; fill: black; font-weight: 500; } .period_title { fill: #121212; } .period { fill: #a1baa1; } .P1 { fill-opacity: 0.5; fill: #0098e5; } .P2 { fill-opacity: 0.5; fill: #7c2b03; } .separator { stroke: black; } .is-true { fill: green} .is-false { fill: red } .range-self .period { fill-opacity: 0.5; fill: #0098e5; } .range-range .period { fill-opacity: 0.5; fill: #7c2b03; } " .replace("\n", ""); } fn svg_translate(x: &i64, y: &i64) -> String { format!("translate({}, {})", x, y) } struct SVGCoordX(i64); impl From for SVGCoordX { fn from(date: PDateTime) -> Self { SVGCoordX(date.signed_duration_since(dates::d01()).num_seconds() / SECOND_PER_PX) } } impl Into for SVGCoordX { fn into(self) -> i64 { self.0 } } struct SVGWidth(i64); impl Into for SVGWidth { fn into(self) -> i64 { self.0 } } struct SVGGroup { group: Group, line_count: i64, } impl AsRef for SVGGroup { fn as_ref(&self) -> &Group { &self.group } } impl Into for SVGGroup { fn into(self) -> Group { self.group } } impl Into for SVGGroup { fn into(self) -> Element { self.group.into() } } struct SVGText(Text); impl From for SVGText { fn from(value: String) -> Self { SVGText(Text::new().add(svg::node::Text::new(value))) } } const PERIOD_DIAGRAMS_PATH: &str = "./diagrams/period/"; const RANGE_DIAGRAMS_PATH: &str = "./diagrams/range/"; pub fn recreate_period_dir() -> io::Result<()> { match fs::remove_dir_all(PERIOD_DIAGRAMS_PATH) { Ok(_) => (), Err(e) => match e.kind() { ErrorKind::NotFound => {} other_error => { panic!("Problem opening the file: {:?}", other_error); } }, }; fs::create_dir_all(PERIOD_DIAGRAMS_PATH) } pub fn recreate_range_dir() -> io::Result<()> { match fs::remove_dir_all(RANGE_DIAGRAMS_PATH) { Ok(_) => (), Err(e) => match e.kind() { ErrorKind::NotFound => {} other_error => { panic!("Problem opening the file: {:?}", other_error); } }, }; fs::create_dir_all(RANGE_DIAGRAMS_PATH) } pub enum SVGTestObject { Period, Range, } pub struct SVGTest { groups: Vec, title: String, object: SVGTestObject, } impl SVGTest { pub fn new(title: &String, object: SVGTestObject) -> Self { Self { title: title.clone(), groups: vec![], object, } } pub fn write(&self) -> &Self { let path = match self.object { SVGTestObject::Period => PERIOD_DIAGRAMS_PATH, SVGTestObject::Range => RANGE_DIAGRAMS_PATH, }; let path = format!("{}/{}.svg", path, self.title); let max_x: i64 = match self.object { SVGTestObject::Period => SVGCoordX::from(dates::d09()).into(), SVGTestObject::Range => SVGCoordX::from(dates::d15()).into(), }; let mut document = Document::new(); document = document.add(Style::new(get_style())); let mut current_y = 0; for svg_group in &self.groups { document = document.add( svg_group .group .clone() .set("transform", svg_translate(&0, ¤t_y)), ); current_y = current_y + LINE_HEIGHT * svg_group.line_count + GAP_PER_CASE; document = document.add( Line::new() .set("class", "separator") .set("x1", 0 + GAP_PER_CASE) .set("x2", max_x - GAP_PER_CASE) .set("y1", current_y - GAP_PER_CASE / 2 - GAP) .set("y2", current_y - GAP_PER_CASE / 2 - GAP), ); } document = document.set("viewBox", (0, 0, max_x, current_y)); svg::save(path, &document).unwrap(); return self; } fn add(mut self, group: SVGGroup) -> Self { self.groups.push(group); self } }