mod component;
mod layer;
mod sequence;
use std::{fs::File, io::Write};
use log::{debug, error, info};
use svg::{Document, node::element::Rectangle};
use orrery_core::{
color::Color,
draw::ArrowWithTextDrawer,
geometry::{Insets, Size},
semantic,
};
use crate::{config::StyleConfig, error::RenderError, export, layout::layer::LayeredLayout};
pub struct SvgBuilder<'a> {
file_name: String,
style: Option<&'a StyleConfig>,
diagram: Option<&'a semantic::Diagram>,
}
pub struct Svg {
file_name: String,
background_color: Option<Color>,
arrow_with_text_drawer: ArrowWithTextDrawer, }
impl<'a> SvgBuilder<'a> {
pub fn new(file_name: &str) -> Self {
Self {
file_name: file_name.to_string(),
style: None,
diagram: None,
}
}
pub fn with_style(mut self, style: &'a StyleConfig) -> Self {
self.style = Some(style);
self
}
pub fn with_diagram(mut self, diagram: &'a semantic::Diagram) -> Self {
self.diagram = Some(diagram);
self
}
pub fn build(self) -> Result<Svg, RenderError> {
let mut background_color = None;
if let Some(diagram) = self.diagram {
if let Some(color) = diagram.background_color() {
background_color = Some(color);
}
} else if let Some(style) = self.style {
background_color = style.background_color().map_err(RenderError::Layout)?;
}
let arrow_with_text_drawer = ArrowWithTextDrawer::new();
Ok(Svg {
file_name: self.file_name,
background_color,
arrow_with_text_drawer,
})
}
}
impl Svg {
pub fn calculate_svg_dimensions(&self, content_size: Size) -> Size {
let margin: f32 = 50.0;
let svg_size = content_size.add_padding(Insets::uniform(margin));
debug!(
"Final SVG dimensions: {}x{}",
svg_size.width(),
svg_size.height()
);
svg_size
}
pub fn add_background(&self, mut doc: Document, size: Size) -> Document {
if let Some(bg_color) = &self.background_color {
let bg = Rectangle::new()
.set("x", 0)
.set("y", 0)
.set("width", size.width())
.set("height", size.height())
.set("fill", bg_color.to_string())
.set("fill-opacity", bg_color.alpha());
doc = doc.add(bg);
}
doc
}
pub fn write_document(&self, doc: Document) -> Result<(), export::Error> {
info!(file_name = self.file_name; "Creating SVG file");
let f = match File::create(&self.file_name) {
Ok(file) => file,
Err(err) => {
error!(file_name=self.file_name, err:err; "Failed to create SVG file");
return Err(export::Error::Io(err));
}
};
if let Err(err) = write!(&f, "{doc}") {
error!(file_name=self.file_name, err:err; "Failed to write SVG content");
return Err(export::Error::Io(err));
}
Ok(())
}
}
impl export::Exporter for Svg {
fn export_layered_layout(&mut self, layout: &LayeredLayout) -> Result<(), export::Error> {
let doc = self.render_layered_layout(layout);
debug!("SVG document rendered for layered layout");
self.write_document(doc)
}
}