diff --git a/Cargo.lock b/Cargo.lock index 2d50223..e0d447b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -19,5 +25,6 @@ name = "vis" version = "0.1.0" dependencies = [ "anyhow", + "once_cell", "unicode-segmentation", ] diff --git a/Cargo.toml b/Cargo.toml index 0b62bf5..9b67628 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2021" [dependencies] anyhow = "1.0" -unicode-segmentation = "1.1" \ No newline at end of file +unicode-segmentation = "1.1" +once_cell = "1.20.2" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b3b4889..3dd8805 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod render; use crate::Element::{Edge, Node}; use parse::tokens::TokenType; use std::collections::HashMap; +use std::fmt::Display; #[derive(Debug)] pub struct Vis { @@ -27,32 +28,34 @@ impl Element { token: TokenType, label: Option, ) -> Element { - Element::Edge(id, source, token, label) + Edge(id, source, token, label) } +} - pub fn to_string(&self) -> String { +impl Display for Element { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Node(id, label, children) => { - let mut s = String::new(); - s.push_str(&format!( + let mut string = String::new(); + string.push_str(&format!( "Node {{{}: {}", id, label.as_ref().unwrap_or(&"".to_string()) )); for child in children { - s.push_str(&format!(" {}", child.to_string())); + string.push_str(&format!(" {}", child)); } - s.push_str("}"); - s + string.push('}'); + write!(f, "{}", string) } Edge(id, source, token, label) => { - let mut s = "Edge {{".to_string(); - s.push_str(&format!("{} {} {:?}", id, source, token)); + let mut string = "Edge {{".to_string(); + string.push_str(&format!("{} {} {:?}", id, source, token)); if let Some(label) = label { - s.push_str(&format!(" {}", label)); + string.push_str(&format!(" {}", label)); } - s.push_str("}"); - s + string.push('}'); + write!(f, "{}", string) } } } diff --git a/src/main.rs b/src/main.rs index cd48301..dc99b67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::fs; use std::fs::File; use std::io::Write; use std::process::exit; -use vis::render::svgrender2::SvgRender; +use vis::render::svg_renderer::SvgRender; use vis::render::Renderer; fn main() -> anyhow::Result<()> { @@ -15,7 +15,7 @@ fn main() -> anyhow::Result<()> { } else { let vis_file = read_file(&args[1])?; let vis = vis::parse::parse_vis(vis_file.as_str())?; - println!("{:?}", vis); + // println!("{:?}", vis); let svg_bytes = SvgRender {}.render(vis)?; let mut file = File::create("bank.svg").expect("Unable to create file"); file.write_all(&svg_bytes).expect("Unable to write data"); diff --git a/src/parse/parser.rs b/src/parse/parser.rs index f595361..e6462a0 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -105,7 +105,6 @@ impl Parser { } fn styles(&mut self) -> anyhow::Result> { - println!("styles"); if self.match_token(Styles) { self.consume(LeftBrace, "Expected '{'")?; let mut styles = vec![]; @@ -120,14 +119,11 @@ impl Parser { } fn style(&mut self) -> anyhow::Result { - println!("style"); if self.check(&Identifier) { let idref = self.peek().lexeme.to_owned(); - println!("id {}", idref); self.advance(); let containertype = self.containertype()?; self.consume(RightParen, "Expected ')'")?; - println!("containertype {:?}", containertype); self.consume(LeftBrace, "Expected '{'")?; let attributes = self.style_elements()?; self.consume(RightBrace, "Expected '}'")?; @@ -143,15 +139,12 @@ impl Parser { } fn style_elements(&mut self) -> anyhow::Result> { - println!("style_elements"); let mut elements = HashMap::new(); let mut key = self.peek().clone(); - println!("key {:?}", key); while key.tokentype != RightBrace { self.advance(); self.consume(Colon, "Expected ':'")?; let value = self.advance().clone(); - println!("value {:?}", value); elements.insert(key.lexeme.to_owned(), value.lexeme); key = self.peek().clone(); } @@ -159,7 +152,6 @@ impl Parser { } fn containertype(&mut self) -> anyhow::Result { - println!("containertype"); Ok(if self.check(&LeftParen) { self.advance(); if self.match_token(Group) { diff --git a/src/parse/scanner.rs b/src/parse/scanner.rs index 9ad014d..87aed3e 100644 --- a/src/parse/scanner.rs +++ b/src/parse/scanner.rs @@ -1,11 +1,21 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; use unicode_segmentation::UnicodeSegmentation; use crate::parse::tokens::{ Token, TokenType::{self, *}, - KEYWORDS, }; +static KEYWORDS: Lazy> = Lazy::new(|| { + HashMap::from([ + ("structure", Structure), + ("styles", Styles), + ("group", Group), + ("px", Px), + ]) +}); + pub fn scan(vis: &str) -> anyhow::Result> { let mut scanner = Scanner::new(vis); scanner.scan(); @@ -126,10 +136,7 @@ impl<'a> Scanner<'a> { self.advance(); } let text = self.chars[self.start_pos..self.current_pos].concat(); - let tokentype = KEYWORDS - .get(text.as_str()) - .map(|d| *d) - .unwrap_or(Identifier); + let tokentype = KEYWORDS.get(text.as_str()).cloned().unwrap_or(Identifier); self.add_token(tokentype); } @@ -175,7 +182,7 @@ impl<'a> Scanner<'a> { return false; } self.current_pos += 1; - return true; + true } fn advance(&mut self) -> &str { @@ -192,18 +199,14 @@ impl<'a> Scanner<'a> { } fn is_digit(char: &str) -> bool { - char.len() > 0 && char.chars().next().unwrap().is_ascii_digit() + !char.is_empty() && char.chars().next().unwrap().is_ascii_digit() } fn is_alpha(char: &str) -> bool { - if char.len() == 0 { + if char.is_empty() { false } else { let char = char.chars().next().unwrap(); - if char.is_alphabetic() || char == '_' { - true - } else { - false - } + char.is_alphabetic() || char == '_' } } diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index cd240a9..3080405 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -1,16 +1,3 @@ -use std::cell::LazyCell; -use std::collections::HashMap; -use TokenType::*; - -pub const KEYWORDS: LazyCell> = LazyCell::new(|| { - let mut m = HashMap::new(); - m.insert("structure", Structure); - m.insert("styles", Styles); - m.insert("group", Group); - m.insert("px", Px); - m -}); - #[derive(Debug, Clone)] pub struct Token { pub tokentype: TokenType, diff --git a/src/render/html.rs b/src/render/html.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/render/mod.rs b/src/render/mod.rs index 1ceabaa..b4cdb27 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,10 +1,7 @@ use crate::Vis; -pub mod html; -mod rendering_svg_elements; -mod svg_renderer; +pub mod svg_renderer; pub mod svglib; -pub mod svgrender2; /// trait for turning the object model into a byte representation pub trait Renderer { diff --git a/src/render/rendering_svg_elements.rs b/src/render/rendering_svg_elements.rs deleted file mode 100644 index d28e6b2..0000000 --- a/src/render/rendering_svg_elements.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::render::svglib::rect::rect; -use crate::render::svglib::svg::{svg, Svg}; -use crate::render::svglib::text::text; -use crate::{Element, Vis}; - -pub fn render_vis_with_grid_layout( - vis: &Vis, - grid_cell_size: f32, - spacing: f32, - padding: f32, -) -> String { - let style = include_str!("svg_node.css"); - let mut svg = svg(); - svg.style(style); - svg.viewbox("0 0 100 100"); // Default size; can adjust dynamically - - // Start recursive layout - let (parent_width, parent_height) = layout_box( - &mut svg, - &vis.structure, - grid_cell_size, - spacing, - padding, - 0.0, - 0.0, - ); - - svg.width(parent_width as usize); - svg.height(parent_height as usize); - svg.to_string() -} - -fn layout_box( - svg: &mut Svg, - elements: &[Element], - grid_cell_size: f32, - spacing: f32, - padding: f32, - start_x: f32, - start_y: f32, -) -> (f32, f32) { - let current_x = start_x + padding; - let current_y = start_y + padding; - let mut max_width: f32 = 0.0; - let mut max_height: f32 = 0.0; - - let grid_cols = (elements.len() as f32).sqrt().ceil() as usize; - - for (i, element) in elements.iter().enumerate() { - let col = i % grid_cols; - let row = i / grid_cols; - - let child_x = current_x + col as f32 * (grid_cell_size + spacing); - let child_y = current_y + row as f32 * (grid_cell_size + spacing); - - match element { - Element::Node(_, label, children) => { - let (_, _) = layout_box( - svg, - children, - grid_cell_size, - spacing, - padding, - child_x, - child_y, - ); - let width = label.as_ref().map(|l| l.len() * 10).unwrap_or(100); - let height = label - .as_ref() - .map(|l| l.lines().collect::().len() * 10) - .unwrap_or(100); - svg.add( - rect() - .x(child_x) - .y(child_y) - .width(width) - .height(height) - .fill("none") - .stroke("green"), - ); - - if let Some(label) = label { - svg.add( - text() - .x(child_x + (width as f32 / 2.0)) - .y(child_y + (height as f32 / 2.0)) - .text(label) - .attr("text-anchor", "middle"), - ); - } - - // Update the bounding box dimensions - max_width = max_width.max(child_x + width as f32 - start_x); - max_height = max_height.max(child_y + height as f32 - start_y); - } - Element::Edge(_, _, _, _) => { - // Edges are processed after all nodes are laid out to connect them - // For now, assume no visual rendering of edges in this example - } - } - } - let total_width = max_width + padding * 2.0; - let total_height = max_height + padding * 2.0; - if elements.len() > 0 { - let parent_box = rect() - .x(start_x) - .y(start_y) - .width(total_width) - .height(total_height) - .fill("none") - .stroke("red") - .attr("stroke-width", "2"); - svg.add(parent_box); - } - - (total_width, total_height) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ContainerType, Element, StyleNode, Vis}; - use std::fs::File; - use std::io::Write; - - #[test] - fn test_render_vis_with_grid_layout() { - // Create a mock StyleNode - let style_node = StyleNode { - id_ref: "node_1".to_string(), - containertype: ContainerType::NonGroup, - attributes: [("fill".to_string(), "white".to_string())] - .iter() - .cloned() - .collect(), - }; - - // Create mock Elements - let element = Element::Node( - "node_1".to_string(), - Some("Node 1".to_string()), - vec![], // No child elements - ); - let element2 = Element::Node( - "node_1".to_string(), - Some("Node 1".to_string()), - vec![], // No child elements - ); - - // Create Vis structure - let vis = Vis { - styles: vec![style_node], - structure: vec![element, element2], - }; - - let svg_output = render_vis_with_grid_layout(&vis, 50.0, 10.0, 5.0); - - let mut file = File::create("output_multiple_nodes.svg").expect("Unable to create file"); - file.write_all(&svg_output.as_bytes().to_vec()) - .expect("Unable to write data"); - } -} diff --git a/src/render/svg_renderer.rs b/src/render/svg_renderer.rs index 35c1f88..50a6293 100644 --- a/src/render/svg_renderer.rs +++ b/src/render/svg_renderer.rs @@ -1,117 +1,134 @@ -// use crate::render::svglib::rect::{rect, Rect}; -// use crate::render::svglib::svg::svg; -// use crate::render::svglib::text::text; -// use crate::render::svglib::Value; -// use crate::render::Renderer; -// use crate::{Element, Vis}; -// use std::fs::File; -// use std::io::Write; -// -// struct SvgRenderer {} -// impl Renderer for SvgRenderer { -// fn render(&self, vis: Vis) -> anyhow::Result> { -// let style = include_str!("svg_node.css"); -// let mut svg = svg(); -// svg.viewbox("0, 0, 300, 300"); -// svg.style(style); -// -// let current_x = start_x + padding; -// let current_y = start_y + padding; -// let mut max_width: f32 = 0.0; -// let mut max_height: f32 = 0.0; -// -// let grid_cols = (elements.len() as f32).sqrt().ceil() as usize; -// -// for e in vis.structure { -// let col = i % grid_cols; -// let row = i / grid_cols; -// -// let child_x = current_x + col as f32 * (grid_cell_size + spacing); -// let child_y = current_y + row as f32 * (grid_cell_size + spacing); -// -// if let Element::Node(_, label, children) = e { -// if let Some(label) = label { -// let mut longest_len = 0; -// for line in label.lines() { -// longest_len = longest_len.max(line.len()); -// } -// let width = longest_len / 2; -// let linecount = label.lines().count(); -// let height = linecount + 1; -// let x = 1; -// let y = ((height - linecount) / 2 + 2) as f64 -// - if linecount == 1 { 0.25 } else { 0.0 }; -// -// let width = label.len() * 10; -// let height = label.lines().collect::().len() * 10; -// svg.add( -// rect() -// .x(child_x) -// .y(child_y) -// .width(width) -// .height(height) -// .fill("none") -// .stroke("green"), -// ); -// -// if let Some(label) = label { -// svg.add( -// text() -// .x(child_x + (width as f32 / 2.0)) -// .y(child_y + (height as f32 / 2.0)) -// .text(label) -// .attr("text-anchor", "middle"), -// ); -// } -// -// svg.add(text); -// } else { -// svg.add(rectangle(0, 0, 70, 30)); -// } -// } -// } -// -// let svg = svg.to_string(); -// -// let mut file = File::create("output.svg")?; -// file.write_all(svg.as_bytes())?; -// Ok(svg.as_bytes().to_vec()) -// } -// } -// -// fn rectangle(x: usize, y: usize, width: V, height: V) -> Rect -// where -// V: Into, -// { -// Rect::new() -// .x(x) -// .y(y) -// .width(width) -// .height(height) -// .class("rect") -// .fill("none") -// } -// -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::Element; -// use std::fs::File; -// use std::io::Write; -// -// #[test] -// fn test_render() { -// let vis = Vis { -// structure: vec![Element::new_node( -// "id", -// Some("blokkendoos\nkoepeon\nknallen\nhond"), -// vec![], -// )], -// styles: vec![], -// }; -// let renderer = SvgRenderer {}; -// let buf = renderer.render(vis).unwrap(); -// let mut file = File::create("output.svg").expect("Unable to create file"); -// file.write_all(&buf).expect("Unable to write data"); -// } -// } +use crate::render::svglib::rect::rect; +use crate::render::svglib::svg::{svg, Svg}; +use crate::render::svglib::text::text; +use crate::render::Renderer; +use crate::Element::Node; +use crate::{Element, StyleNode, Vis}; + +pub struct SvgRender; + +impl Renderer for SvgRender { + fn render(&self, vis: Vis) -> anyhow::Result> { + let mut svg = svg(); + + svg.width(100); + svg.height(100); + let style = include_str!("svg_node.css"); + svg.style(style); + let (w, h) = render_elements(&mut svg, &vis.structure, &vis.styles, 0, 0); + svg.viewbox(format!("0 0 {} {}", w, h)); + Ok(svg.to_string().into_bytes()) + } +} + +fn render_elements( + svg: &mut Svg, + elements: &Vec, + _styles: &Vec, + start_x: u32, + start_y: u32, +) -> (u32, u32) { + let padding = 15; + + let mut total_width = 0; + let mut total_height = 0; + let mut x = start_x; + let y = start_y; + + for element in elements { + if let Node(id, label, children) = element { + let (width, height) = if !children.is_empty() { + render_elements(svg, children, _styles, x + padding, y + padding) + } else if let Node(_, Some(label), _) = element { + let label_width = label.lines().map(|l| l.len()).max().unwrap_or(0) as u32; + let label_height = label.lines().count() as u32; + ((label_width as f32 * 4.9 + 8.0) as u32, label_height * 15) + } else { + (0, 0) + }; + + total_width += width + padding; + total_height = u32::max(total_height, height + padding); + + svg.add( + rect() + .id(id) + .x(x + padding) + .y(y + padding) + .width(width) + .height(height) + .stroke("white") + .fill("none"), + ); + + svg.add( + text() + .x(x + padding + width / 2) + .y(start_y + padding) + .fill("white") + .text(label.as_ref().map(|l| l.as_str()).unwrap_or("")) + .attr("text-anchor", "middle") + .attr("stroke-width", "1") + .class("node"), + ); + x += width + padding; + } + } + + (total_width + padding, total_height + padding) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ContainerType, StyleNode, Vis}; + use std::fs::File; + use std::io::Write; + + #[test] + fn test_render_vis_with_grid_layout() { + // Create a mock StyleNode + let style_node = StyleNode { + id_ref: "node_1".to_string(), + containertype: ContainerType::NonGroup, + attributes: [("fill".to_string(), "white".to_string())] + .iter() + .cloned() + .collect(), + }; + + // Create mock Elements + let element1 = Node( + "node1".to_string(), + Some("Node 1\nlonger".to_string()), + vec![], // No child elements + ); + let element2 = Node( + "node2".to_string(), + Some("Node 2".to_string()), + vec![], // No child elements + ); + + let element3 = Node( + "node3".to_string(), + Some("Node 3\nis longer\nthan you are to me".to_string()), + vec![], // No child elements + ); + let root = Node( + "root".to_string(), + Some("root".to_string()), + vec![element1, element2, element3], + ); + + // Create Vis structure + let vis = Vis { + styles: vec![style_node], + structure: vec![root], + }; + + let svg_output = SvgRender {}.render(vis).unwrap(); + + let mut file = File::create("output_multiple_nodes.svg").expect("Unable to create file"); + file.write_all(&svg_output).expect("Unable to write data"); + } +} diff --git a/src/render/svglib/circle.rs b/src/render/svglib/circle.rs index 37a4c91..551ef15 100644 --- a/src/render/svglib/circle.rs +++ b/src/render/svglib/circle.rs @@ -1,16 +1,13 @@ use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Shape, Value}; pub fn circle() -> Circle { - Circle::new() + Circle::default() } +#[derive(Debug, Default, Clone)] pub struct Circle(Vec); impl Circle { - pub fn new() -> Self { - Self(vec![]) - } - pub fn id>(mut self, id: V) -> Self { self.0.push(att("id", id)); self diff --git a/src/render/svglib/div.rs b/src/render/svglib/div.rs index f11bbfc..ecac771 100644 --- a/src/render/svglib/div.rs +++ b/src/render/svglib/div.rs @@ -1,22 +1,15 @@ use crate::render::svglib::{att, att_to_string, Att, Value}; - +use std::fmt; pub fn div() -> Div { - Div::new() + Div::default() } - +#[derive(Debug, Clone, Default)] pub struct Div { atts: Vec, child: String, } impl Div { - pub fn new() -> Self { - Self { - atts: vec![], - child: "".to_string(), - } - } - pub fn id>(&mut self, id: V) { self.atts.push(att("id", id)); } @@ -34,15 +27,18 @@ impl Div { self } - pub fn to_string(&self) -> String { - format!( + pub fn atts(&self) -> &[Att] { + &self.atts + } +} + +impl fmt::Display for Div { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, r#"
{}
"#, self.atts.iter().map(att_to_string).collect::(), self.child ) } - - fn atts(&self) -> &[Att] { - &self.atts - } } diff --git a/src/render/svglib/foreign_object.rs b/src/render/svglib/foreign_object.rs index 34e809f..385a290 100644 --- a/src/render/svglib/foreign_object.rs +++ b/src/render/svglib/foreign_object.rs @@ -2,22 +2,16 @@ use crate::render::svglib::div::Div; use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; pub fn foreign_object() -> ForeignObject { - ForeignObject::new() + ForeignObject::default() } +#[derive(Default, Debug, Clone)] pub struct ForeignObject { child: Option
, //for now atts: Vec, } impl ForeignObject { - pub fn new() -> Self { - Self { - child: None, - atts: vec![], - } - } - pub fn id(&mut self, id: V) where V: Into, @@ -47,7 +41,7 @@ impl ForeignObject { self } - pub fn add(mut self, child: Div) -> Self { + pub fn add_child(mut self, child: Div) -> Self { self.child = Some(child); self } @@ -65,10 +59,7 @@ impl Element for ForeignObject { fn to_string(&self) -> String { format!( r#"{}"#, - self.atts - .iter() - .map(|a| att_to_string(a)) - .collect::(), + self.atts.iter().map(att_to_string).collect::(), self.child .as_ref() .map(|c| c.to_string()) diff --git a/src/render/svglib/image.rs b/src/render/svglib/image.rs index 9fbf1ad..56a7d21 100644 --- a/src/render/svglib/image.rs +++ b/src/render/svglib/image.rs @@ -1,18 +1,15 @@ use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; pub fn image() -> Image { - Image::new() + Image::default() } +#[derive(Default)] pub struct Image { atts: Vec, } impl Image { - pub fn new() -> Self { - Self { atts: vec![] } - } - pub fn id>(&mut self, id: V) { self.atts.push(att("id", id)); } diff --git a/src/render/svglib/link.rs b/src/render/svglib/link.rs index 58f3522..58deb9b 100644 --- a/src/render/svglib/link.rs +++ b/src/render/svglib/link.rs @@ -1,9 +1,7 @@ use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; pub fn link(href: &str) -> Link { - let mut atts = vec![]; - atts.push(att("href", href)); - Link(atts) + Link(vec![att("href", href)]) } pub struct Link(Vec); diff --git a/src/render/svglib/mod.rs b/src/render/svglib/mod.rs index 7f025cf..5b2bde2 100644 --- a/src/render/svglib/mod.rs +++ b/src/render/svglib/mod.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + pub mod circle; pub mod div; pub mod ellipse; @@ -20,9 +22,9 @@ impl From<&str> for Value { } } -impl Value { - pub fn to_string(&self) -> String { - self.0.clone() +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) } } @@ -94,7 +96,7 @@ pub trait Shape { V: Into; } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Att { name: String, value: Value, @@ -117,12 +119,5 @@ where } fn att_to_string(att: &Att) -> String { - format!(r#" {}="{}""#, att.name, att.value.to_string()) -} - -fn att_str2(att_name: &str, att_val: &Option) -> String { - att_val - .as_ref() - .map(|val| format!(r#" {}="{}""#, att_name, val)) - .unwrap_or("".to_string()) + format!(r#" {}="{}""#, att.name, att.value) } diff --git a/src/render/svglib/path.rs b/src/render/svglib/path.rs index f5361c0..f078584 100644 --- a/src/render/svglib/path.rs +++ b/src/render/svglib/path.rs @@ -31,10 +31,7 @@ impl Path { } } - fn id(&mut self, id: V) - where - V: Into, - { + pub fn id>(&mut self, id: V) { self.atts.push(att("id", id)); } diff --git a/src/render/svglib/rect.rs b/src/render/svglib/rect.rs index 077214c..3263ffe 100644 --- a/src/render/svglib/rect.rs +++ b/src/render/svglib/rect.rs @@ -1,17 +1,15 @@ use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; pub fn rect() -> Rect { - Rect::new() + Rect::default() } +#[derive(Debug, Clone, Default)] pub struct Rect { atts: Vec, } impl Rect { - pub fn new() -> Self { - Self { atts: vec![] } - } pub fn id>(mut self, id: V) -> Self { self.atts.push(att("id", id)); self diff --git a/src/render/svglib/svg.rs b/src/render/svglib/svg.rs index e7744f7..9c53139 100644 --- a/src/render/svglib/svg.rs +++ b/src/render/svglib/svg.rs @@ -1,9 +1,11 @@ use crate::render::svglib::{att, att_to_string, Att, Element, Value}; +use std::fmt; pub fn svg() -> Svg { - Svg::new() + Svg::default() } +#[derive(Default)] pub struct Svg { style: Option, elements: Vec>, @@ -11,14 +13,6 @@ pub struct Svg { } impl Svg { - pub fn new() -> Self { - Self { - style: None, - elements: vec![], - atts: vec![], - } - } - pub fn style(&mut self, style: &str) { self.style = Some(style.to_string()); } @@ -47,26 +41,24 @@ impl Svg { pub fn transform>(&mut self, transform: V) { self.atts.push(att("transform", transform)); } +} - pub fn to_string(&self) -> String { - let mut svg = String::new(); - svg.push_str( - format!( - r#"{}"#, - self.atts.iter().map(att_to_string).collect::(), - self.style - .as_ref() - .map(|s| format!("", s.to_string())) - .unwrap_or("".to_string()) - ) - .as_str(), - ); +impl fmt::Display for Svg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + r#"{}"#, + self.atts.iter().map(att_to_string).collect::(), + self.style + .as_ref() + .map(|s| format!("", s)) + .unwrap_or("".to_string()) + )?; for e in &self.elements { - svg.push_str(e.to_string().as_str()); + write!(f, "{}", e.to_string().as_str())?; } - svg.push_str(""); - svg + write!(f, "") } } @@ -77,7 +69,7 @@ mod tests { #[test] fn style() { - let mut svg = Svg::new(); + let mut svg = svg(); svg.style(".id { background-color: red; }"); assert_eq!( r#""#, @@ -87,7 +79,7 @@ mod tests { #[test] fn add_rect() { - let mut svg = Svg::new(); + let mut svg = svg(); svg.preserveaspectratio("none"); svg.add(rect().x(0).y(0).width(10).height(10)); assert_eq!( diff --git a/src/render/svglib/text.rs b/src/render/svglib/text.rs index 3eb2472..c968dd5 100644 --- a/src/render/svglib/text.rs +++ b/src/render/svglib/text.rs @@ -1,7 +1,8 @@ use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; +use std::fmt::Write; pub fn text() -> Text { - Text::new() + Text::default() } pub struct Text { @@ -9,14 +10,16 @@ pub struct Text { text: String, } -impl Text { - pub fn new() -> Self { +impl Default for Text { + fn default() -> Self { Self { atts: vec![], text: "".to_string(), } } +} +impl Text { pub fn text>(mut self, text: V) -> Self { self.text = text.into(); self @@ -77,15 +80,17 @@ impl Element for Text { } fn to_string(&self) -> String { - let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect(); - let x: String = x[0].value.to_string(); + // let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect(); + // let x: String = x[0].value.to_string(); format!( r#"{}"#, self.atts.iter().map(att_to_string).collect::(), - self.text - .lines() - .map(|l| format!("{}", x, l)) - .collect::(), + self.text.lines().fold(String::new(), |mut output, l| { + let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect(); + let x: String = x[0].value.to_string(); + let _ = write!(output, "{}", x, l); + output + }) ) } diff --git a/src/render/svgrender2.rs b/src/render/svgrender2.rs deleted file mode 100644 index 957d89b..0000000 --- a/src/render/svgrender2.rs +++ /dev/null @@ -1,145 +0,0 @@ -use crate::render::svglib::rect::rect; -use crate::render::svglib::svg::{svg, Svg}; -use crate::render::svglib::text::text; -use crate::render::Renderer; -use crate::Element::Node; -use crate::{Element, StyleNode, Vis}; - -pub struct SvgRender; - -impl Renderer for SvgRender { - fn render(&self, vis: Vis) -> anyhow::Result> { - let mut svg = svg(); - - svg.width(100); - svg.height(100); - let style = include_str!("svg_node.css"); - svg.style(style); - let (w, h) = render_elements(&mut svg, &vis.structure, &vis.styles, 0, 0); - svg.viewbox(format!("0 0 {} {}", w, h)); - Ok(svg.to_string().into_bytes()) - } -} - -fn render_elements( - svg: &mut Svg, - elements: &Vec, - _styles: &Vec, - start_x: u32, - start_y: u32, -) -> (u32, u32) { - let padding = 15; - - let mut total_width = 0; - let mut total_height = 0; - let mut x = start_x; - let y = start_y; - - let mut width1 = 0; - let mut height1 = 0; - - println!("width1: {}, height1: {}", width1, height1); - - for element in elements { - println!("{}", element.to_string()); - if let Node(id, label, children) = element { - let (width, height) = if children.len() > 0 { - render_elements(svg, children, _styles, x + padding, y + padding) - } else { - if let Node(_, Some(label), _) = element { - let label_width = label.lines().map(|l| l.len()).max().unwrap_or(0) as u32; - let label_height = label.lines().count() as u32; - height1 = label_height * 15; - width1 = label_width * 5 + 5; - println!("label {}: {}x{}", label, label_width, label_height); - (width1, height1) - } else { - (0, 0) - } - }; - - total_width += width + padding; - total_height = u32::max(total_height, height + padding); - - svg.add( - rect() - .id(id) - .x(x + padding) - .y(y + padding) - .width(width) - .height(height) - .stroke("white") - .fill("none"), - ); - - svg.add( - text() - .x(x + padding + width / 2) - .y(start_y + padding) - .fill("white") - .text(label.as_ref().map(|l| l.as_str()).unwrap_or("")) - .attr("text-anchor", "middle") - .attr("stroke-width", "1") - .class("node"), - ); - x += width + padding; - } - } - - (total_width + padding, total_height + padding) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ContainerType, StyleNode, Vis}; - use std::fs::File; - use std::io::Write; - - #[test] - fn test_render_vis_with_grid_layout() { - // Create a mock StyleNode - let style_node = StyleNode { - id_ref: "node_1".to_string(), - containertype: ContainerType::NonGroup, - attributes: [("fill".to_string(), "white".to_string())] - .iter() - .cloned() - .collect(), - }; - - // Create mock Elements - let element1 = Node( - "node1".to_string(), - Some("Node 1\nlonger".to_string()), - vec![], // No child elements - ); - let element2 = Node( - "node2".to_string(), - Some("Node 2".to_string()), - vec![], // No child elements - ); - - let element3 = Node( - "node3".to_string(), - Some("Node 3\nis longer\nthan you are to me".to_string()), - vec![], // No child elements - ); - let root = Node( - "root".to_string(), - Some("root".to_string()), - vec![element1, element2, element3], - ); - - // Create Vis structure - let vis = Vis { - styles: vec![style_node], - structure: vec![root], - }; - - let svg_output = SvgRender {}.render(vis).unwrap(); - - let mut file = File::create("output_multiple_nodes.svg").expect("Unable to create file"); - file.write_all(&svg_output).expect("Unable to write data"); - } -}