From 5e5ce9541ce9e1643b10c151524fbdc28b39e3c5 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sun, 26 Jan 2025 17:49:53 +0100 Subject: [PATCH] added vertical layout --- Cargo.lock | 7 +++ Cargo.toml | 3 +- bank.svg | 15 ----- examples/bank_architecture/bank.vis | 9 +-- output_multiple_nodes.svg | 15 ----- src/lib.rs | 93 +++++++++++++++++++++++++---- src/parse/parser.rs | 51 +++++++++------- src/render/svg_renderer.rs | 50 +++++++++++++--- src/render/svglib/ellipse.rs | 1 - src/render/svglib/text.rs | 4 +- 10 files changed, 164 insertions(+), 84 deletions(-) delete mode 100644 bank.svg delete mode 100644 output_multiple_nodes.svg diff --git a/Cargo.lock b/Cargo.lock index e0d447b..dcd3410 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 = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + [[package]] name = "once_cell" version = "1.20.2" @@ -25,6 +31,7 @@ name = "vis" version = "0.1.0" dependencies = [ "anyhow", + "log", "once_cell", "unicode-segmentation", ] diff --git a/Cargo.toml b/Cargo.toml index 9b67628..861b1b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" [dependencies] anyhow = "1.0" unicode-segmentation = "1.1" -once_cell = "1.20.2" \ No newline at end of file +once_cell = "1.20.2" +log = "0.4" \ No newline at end of file diff --git a/bank.svg b/bank.svg deleted file mode 100644 index 8a17733..0000000 --- a/bank.svg +++ /dev/null @@ -1,15 +0,0 @@ -Calculation />Account interest Calculation />Interest Rates />Configuration />NoB Execution />Collection of Reinstatement instructions />NoB />Reporting />Bank Motor />Bank Scripts />Bank Client />Bank DB />Bank />InterestEngine /> \ No newline at end of file diff --git a/examples/bank_architecture/bank.vis b/examples/bank_architecture/bank.vis index 77f3050..8c2067e 100644 --- a/examples/bank_architecture/bank.vis +++ b/examples/bank_architecture/bank.vis @@ -28,14 +28,11 @@ structure { } styles { - structure(group){ - orientation: vertical - } lanes(group) { type: textnode - orientation: horizontal + orientation: vertical shape: rectangle } - functions(group) {} - systems(group) {} + functions(group) {orientation: horizontal} + systems(group) {orientation: horizontal} } diff --git a/output_multiple_nodes.svg b/output_multiple_nodes.svg deleted file mode 100644 index 246734f..0000000 --- a/output_multiple_nodes.svg +++ /dev/null @@ -1,15 +0,0 @@ -Node 1longer />Node 2 />Node 3is longerthan you are to me /> \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index c778df9..0efe413 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,37 +2,77 @@ pub mod parse; pub mod render; use crate::parse::tokens::TokenType; +use log::debug; use std::collections::HashMap; use std::fmt::Display; +pub const BODY: &str = "structure"; +pub const DEFAULT_ORIENTATION: &str = "horizontal"; +pub const ORIENTATION: &str = "orientation"; +pub const HORIZONTAL: &str = "horizontal"; +pub const VERTICAL: &str = "vertical"; + #[derive(Debug)] pub struct Vis { - pub structure: VisNode, + pub structure: Vec, pub styles: Vec, } impl Vis { - pub fn get_node(&self, id: &str) -> Option<&VisNode> { - if self.structure.id == id { - return Some(&self.structure); + pub fn new(structure: VisNode, styles: Vec) -> Self { + let mut vis = Self { + structure: vec![structure], + styles, + }; + + // set defaults + let bodystyle = vis.get_style_node_mut(BODY); + if let Some(bodystyle) = bodystyle { + bodystyle.set_attribute_if_absent(ORIENTATION, DEFAULT_ORIENTATION); } else { - for child in &self.structure.children { - if child.id == id { - return Some(child); - } + let mut bodystyle = StyleNode::new(BODY, ContainerType::Group); + bodystyle.set_attribute(ORIENTATION, DEFAULT_ORIENTATION); + vis.styles.push(bodystyle); + } + + vis + } + + pub fn get_node(&self, id: impl Into) -> Option<&VisNode> { + Self::get_node_recurse(id, &self.structure) + } + + fn get_node_recurse(id: impl Into, elements: &Vec) -> Option<&VisNode> { + let id = id.into(); + for element in elements { + if let Some(e) = Self::get_node_recurse(id.clone(), &element.children) { + return Some(e); + } else if element.id == id { + return Some(element); } } None } - pub fn get_styles(&self, id: &str) -> HashMap { + pub fn get_style_node_mut(&mut self, id_ref: &str) -> Option<&mut StyleNode> { + self.styles.iter_mut().find(|s| s.id_ref == id_ref) + } + + pub fn get_style_value(&self, idref: impl Into, key: &str) -> Option { + self.get_styles(idref).get(key).map(String::to_string) + } + + pub fn get_styles(&self, id: impl Into) -> HashMap { // println!("get_styles {:?}", id); let mut styles = HashMap::new(); - self.get_styles2(id, &mut styles); + self.get_styles_recurse(id, &mut styles); + debug!("found {:?}", styles); styles } - fn get_styles2(&self, id: &str, styles: &mut HashMap) { + fn get_styles_recurse(&self, id: impl Into, styles: &mut HashMap) { + let id = id.into(); + debug!("get style for {}", id); let node = self.get_node(id); if let Some(node) = node { // println!("node {:?}", node); @@ -45,7 +85,7 @@ impl Vis { }); } if let Some(parent) = &node.parent { - self.get_styles2(parent, styles); + self.get_styles_recurse(parent, styles); } } } @@ -144,6 +184,35 @@ pub struct StyleNode { pub attributes: HashMap, } +impl StyleNode { + fn new(id_ref: impl Into, containertype: ContainerType) -> Self { + Self { + id_ref: id_ref.into(), + containertype, + attributes: HashMap::new(), + } + } + + pub fn has_attribute(&self, key: &str) -> bool { + self.attributes.contains_key(key) + } + pub fn get_attribute(&self, key: &str) -> Option<&String> { + self.attributes.get(key) + } + pub fn get_attribute_string(&self, key: &str) -> Option { + self.attributes.get(key).map(|s| s.to_owned()) + } + pub fn set_attribute(&mut self, key: impl Into, value: impl Into) { + self.attributes.insert(key.into(), value.into()); + } + + pub fn set_attribute_if_absent(&mut self, key: impl Into, value: impl Into) { + let key = key.into(); + if !self.has_attribute(&key) { + self.set_attribute(key, value); + } + } +} #[derive(Debug, Clone)] pub enum ContainerType { NonGroup, // needs thinking about diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 5aa44af..86ec63b 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -6,23 +6,24 @@ use crate::{ ContainerType, StyleNode, Vis, VisNode, }; use anyhow::anyhow; +use log::debug; use std::collections::HashMap; pub fn parse_vis(contents: &str) -> anyhow::Result { let tokens = crate::parse::scanner::scan(contents)?; - // println!("{:?}", tokens); + debug!("{:?}", tokens); let mut parser = Parser::new(tokens); let structure = parser.structure()?; let styles = parser.styles()?; - // println!("parsed styles{:?}", styles); - let mut vis = Vis { - structure: VisNode::new_node("structure", None::, structure), + debug!("parsed styles{:?}", styles); + let mut vis = Vis::new( + VisNode::new_node("structure", None::, structure), styles, - }; + ); // add bottom up references - vis.structure.children.iter_mut().for_each(|node| { + vis.structure.iter_mut().for_each(|node| { let c = node.clone(); node.children.iter_mut().for_each(|child| { child.parent.replace(c.id.to_owned()); @@ -51,7 +52,7 @@ impl Parser { } fn elements(&mut self) -> anyhow::Result> { - // println!("nodes {:?}", self.peek()); + debug!("nodes {:?}", self.peek()); self.consume(LeftBrace, "Expected '{'")?; let mut nodes = vec![]; while !self.match_token(RightBrace) { @@ -115,7 +116,7 @@ impl Parser { } fn styles(&mut self) -> anyhow::Result> { - // println!("styles {:?}", self.peek()); + debug!("styles {:?}", self.peek()); if self.match_token(Styles) { self.consume(LeftBrace, "Expected '{'")?; let mut styles = vec![]; @@ -130,7 +131,7 @@ impl Parser { } fn style(&mut self) -> anyhow::Result { - // println!("style {:?}", self.peek()); + debug!("style {:?}", self.peek()); if self.check(&Identifier) || self.check(&Structure) { let idref = if self.check(&Structure) { // only structure element can also be referenced @@ -138,17 +139,15 @@ impl Parser { } else { self.peek().lexeme.to_owned() }; - // println!("idref {:?}", idref); + debug!("idref {:?}", idref); self.advance(); let containertype = self.containertype()?; - // println!("containertype {:?}", containertype); - self.consume(RightParen, "Expected ')'")?; if self.peek().tokentype == Colon { // optional self.advance(); } self.consume(LeftBrace, "Expected '{'")?; - // println!("attributes {:?}", self.peek()); + debug!("attributes {:?}", self.peek()); let attributes = self.style_elements()?; self.consume(RightBrace, "Expected '}'")?; @@ -163,14 +162,14 @@ impl Parser { } fn style_elements(&mut self) -> anyhow::Result> { - // println!("read attributes {:?}", self.peek()); + debug!("read attributes {:?}", self.peek()); let mut elements = HashMap::new(); let mut key = self.peek().clone(); while key.tokentype != RightBrace { self.advance(); self.consume(Colon, "Expected ':'")?; let value = self.advance().clone(); - elements.insert(key.lexeme.to_owned(), strip_surrounding(value.lexeme)); + elements.insert(key.lexeme.to_owned(), value.lexeme); key = self.peek().clone(); } Ok(elements) @@ -180,8 +179,10 @@ impl Parser { Ok(if self.check(&LeftParen) { self.advance(); if self.match_token(Group) { + self.consume(RightParen, "Expected ')'")?; ContainerType::Group } else { + self.consume(RightParen, "Expected ')'")?; ContainerType::NonGroup } } else { @@ -268,8 +269,12 @@ mod tests { } } styles { + structure { + adam: eve + orientation: horizontal; + } top(group) { - orientation: "vertical"; + orientation: vertical; } } "#; @@ -279,13 +284,13 @@ mod tests { if let Some(vis) = vis { assert_eq!( - vis.structure.children.len(), + vis.structure[0].children.len(), 1, "The structure should contain one top-level element" ); - if vis.structure.children[0].node_type == Node { - let top = &vis.structure.children[0]; + if vis.structure[0].children[0].node_type == Node { + let top = &vis.structure[0].children[0]; assert_eq!(top.id, "top", "The ID of the first node should be 'top'"); assert_eq!( top.label.as_ref().unwrap(), @@ -309,12 +314,12 @@ mod tests { } else { panic!("The top-level element should be a Node"); } - assert_eq!(vis.styles.len(), 1); + assert_eq!(vis.styles.len(), 2); let styles = vis.get_styles("top"); - assert_eq!(styles.len(), 1); - assert_eq!(styles["orientation"], "vertical"); + assert_eq!(styles.len(), 2); + assert_eq!(styles["orientation"], "vertical"); // overrides parent style } else { - panic!("Parsed structure was unexpectedly None"); + panic!("Parsed structure was None"); } } } diff --git a/src/render/svg_renderer.rs b/src/render/svg_renderer.rs index fd9c7ae..cb3d2c6 100644 --- a/src/render/svg_renderer.rs +++ b/src/render/svg_renderer.rs @@ -2,7 +2,8 @@ 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::{StyleNode, Vis, VisNode}; +use crate::{StyleNode, Vis, VisNode, BODY, HORIZONTAL, ORIENTATION}; +use log::debug; pub struct SvgRender; @@ -14,14 +15,17 @@ impl Renderer for SvgRender { svg.height(100); let style = include_str!("svg_node.css"); svg.style(style); - let (w, h) = render_elements(&mut svg, &vis.structure.children, &vis.styles, 0, 0); - svg.viewbox(format!("0 0 {} {}", w, h)); + let (width, height) = + render_elements(&vis, &mut svg, BODY, &vis.structure, &vis.styles, 0, 0); + svg.viewbox(format!("0 0 {} {}", width, height)); Ok(svg.to_string().into_bytes()) } } fn render_elements( + vis: &Vis, svg: &mut Svg, + parent: &str, elements: &[VisNode], _styles: &Vec, start_x: u32, @@ -32,11 +36,19 @@ fn render_elements( let mut total_width = 0; let mut total_height = 0; let mut x = start_x; - let y = start_y; + let mut y = start_y; for element in elements { let (width, height) = if !element.children.is_empty() { - render_elements(svg, &element.children, _styles, x + padding, y + padding) + render_elements( + vis, + svg, + element.id.as_str(), + &element.children, + _styles, + x + padding, + y + padding, + ) } else if let Some(label) = element.label.as_ref() { let label_width = label.lines().map(|l| l.len()).max().unwrap_or(0) as u32; let label_height = label.lines().count() as u32; @@ -45,8 +57,18 @@ fn render_elements( (0, 0) }; - total_width += width + padding; - total_height = u32::max(total_height, height + padding); + debug!( + "{}:{:?}", + &element.id, + vis.get_style_value(&element.id, ORIENTATION) + ); + if is_horizontal_orientation(vis, parent) { + total_width += width + padding; + total_height = u32::max(total_height, height + padding); + } else { + total_width = u32::max(total_width, width + padding); + total_height += height + padding; + } svg.add( rect() @@ -69,12 +91,22 @@ fn render_elements( .attr("stroke-width", "1") .class("node"), ); - x += width + padding; + if is_horizontal_orientation(vis, parent) { + x += width + padding; + } else { + y += height + padding; + } } (total_width + padding, total_height + padding) } +fn is_horizontal_orientation(vis: &Vis, id: impl Into) -> bool { + let hor = vis.get_style_value(id, ORIENTATION); + let hor = hor.as_deref(); + hor == Some(HORIZONTAL) || hor == None +} + #[cfg(test)] mod tests { use super::*; @@ -120,7 +152,7 @@ mod tests { // Create Vis structure let vis = Vis { styles: vec![style_node], - structure: root, + structure: vec![root], }; let svg_output = SvgRender {}.render(vis).unwrap(); diff --git a/src/render/svglib/ellipse.rs b/src/render/svglib/ellipse.rs index 20106ef..5691ccf 100644 --- a/src/render/svglib/ellipse.rs +++ b/src/render/svglib/ellipse.rs @@ -74,7 +74,6 @@ mod tests { #[test] fn test_ellipse() { let ellipse = ellipse().cx(0).cy(0).rx(10).ry(15); - println!("{:?}", ellipse); assert_eq!( r#""#, ellipse.to_string() diff --git a/src/render/svglib/text.rs b/src/render/svglib/text.rs index fc90317..3f01a1a 100644 --- a/src/render/svglib/text.rs +++ b/src/render/svglib/text.rs @@ -78,7 +78,7 @@ impl Display for Text { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - r#""#, + r#"{}"#, self.atts.iter().map(att_to_string).collect::(), self.text.lines().fold(String::new(), |mut output, l| { let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect(); @@ -108,7 +108,7 @@ mod tests { fn test_rect() { let text = text().x(0).y(0).width(10).height(10); assert_eq!( - r#""#, + r#""#, text.to_string() ) }