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 @@
-
\ 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 @@
-
\ 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()
)
}