lot of cleaning and improving

This commit is contained in:
Shautvast 2025-01-22 18:09:15 +01:00
parent 32c9bc5b40
commit f02d8e419e
22 changed files with 241 additions and 575 deletions

7
Cargo.lock generated
View file

@ -8,6 +8,12 @@ version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.12.0" version = "1.12.0"
@ -19,5 +25,6 @@ name = "vis"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"once_cell",
"unicode-segmentation", "unicode-segmentation",
] ]

View file

@ -6,3 +6,4 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
unicode-segmentation = "1.1" unicode-segmentation = "1.1"
once_cell = "1.20.2"

View file

@ -4,6 +4,7 @@ pub mod render;
use crate::Element::{Edge, Node}; use crate::Element::{Edge, Node};
use parse::tokens::TokenType; use parse::tokens::TokenType;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display;
#[derive(Debug)] #[derive(Debug)]
pub struct Vis { pub struct Vis {
@ -27,32 +28,34 @@ impl Element {
token: TokenType, token: TokenType,
label: Option<String>, label: Option<String>,
) -> Element { ) -> 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 { match self {
Node(id, label, children) => { Node(id, label, children) => {
let mut s = String::new(); let mut string = String::new();
s.push_str(&format!( string.push_str(&format!(
"Node {{{}: {}", "Node {{{}: {}",
id, id,
label.as_ref().unwrap_or(&"".to_string()) label.as_ref().unwrap_or(&"".to_string())
)); ));
for child in children { for child in children {
s.push_str(&format!(" {}", child.to_string())); string.push_str(&format!(" {}", child));
} }
s.push_str("}"); string.push('}');
s write!(f, "{}", string)
} }
Edge(id, source, token, label) => { Edge(id, source, token, label) => {
let mut s = "Edge {{".to_string(); let mut string = "Edge {{".to_string();
s.push_str(&format!("{} {} {:?}", id, source, token)); string.push_str(&format!("{} {} {:?}", id, source, token));
if let Some(label) = label { if let Some(label) = label {
s.push_str(&format!(" {}", label)); string.push_str(&format!(" {}", label));
} }
s.push_str("}"); string.push('}');
s write!(f, "{}", string)
} }
} }
} }

View file

@ -4,7 +4,7 @@ use std::fs;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::process::exit; use std::process::exit;
use vis::render::svgrender2::SvgRender; use vis::render::svg_renderer::SvgRender;
use vis::render::Renderer; use vis::render::Renderer;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
@ -15,7 +15,7 @@ fn main() -> anyhow::Result<()> {
} else { } else {
let vis_file = read_file(&args[1])?; let vis_file = read_file(&args[1])?;
let vis = vis::parse::parse_vis(vis_file.as_str())?; let vis = vis::parse::parse_vis(vis_file.as_str())?;
println!("{:?}", vis); // println!("{:?}", vis);
let svg_bytes = SvgRender {}.render(vis)?; let svg_bytes = SvgRender {}.render(vis)?;
let mut file = File::create("bank.svg").expect("Unable to create file"); let mut file = File::create("bank.svg").expect("Unable to create file");
file.write_all(&svg_bytes).expect("Unable to write data"); file.write_all(&svg_bytes).expect("Unable to write data");

View file

@ -105,7 +105,6 @@ impl Parser {
} }
fn styles(&mut self) -> anyhow::Result<Vec<StyleNode>> { fn styles(&mut self) -> anyhow::Result<Vec<StyleNode>> {
println!("styles");
if self.match_token(Styles) { if self.match_token(Styles) {
self.consume(LeftBrace, "Expected '{'")?; self.consume(LeftBrace, "Expected '{'")?;
let mut styles = vec![]; let mut styles = vec![];
@ -120,14 +119,11 @@ impl Parser {
} }
fn style(&mut self) -> anyhow::Result<StyleNode> { fn style(&mut self) -> anyhow::Result<StyleNode> {
println!("style");
if self.check(&Identifier) { if self.check(&Identifier) {
let idref = self.peek().lexeme.to_owned(); let idref = self.peek().lexeme.to_owned();
println!("id {}", idref);
self.advance(); self.advance();
let containertype = self.containertype()?; let containertype = self.containertype()?;
self.consume(RightParen, "Expected ')'")?; self.consume(RightParen, "Expected ')'")?;
println!("containertype {:?}", containertype);
self.consume(LeftBrace, "Expected '{'")?; self.consume(LeftBrace, "Expected '{'")?;
let attributes = self.style_elements()?; let attributes = self.style_elements()?;
self.consume(RightBrace, "Expected '}'")?; self.consume(RightBrace, "Expected '}'")?;
@ -143,15 +139,12 @@ impl Parser {
} }
fn style_elements(&mut self) -> anyhow::Result<HashMap<String, String>> { fn style_elements(&mut self) -> anyhow::Result<HashMap<String, String>> {
println!("style_elements");
let mut elements = HashMap::new(); let mut elements = HashMap::new();
let mut key = self.peek().clone(); let mut key = self.peek().clone();
println!("key {:?}", key);
while key.tokentype != RightBrace { while key.tokentype != RightBrace {
self.advance(); self.advance();
self.consume(Colon, "Expected ':'")?; self.consume(Colon, "Expected ':'")?;
let value = self.advance().clone(); let value = self.advance().clone();
println!("value {:?}", value);
elements.insert(key.lexeme.to_owned(), value.lexeme); elements.insert(key.lexeme.to_owned(), value.lexeme);
key = self.peek().clone(); key = self.peek().clone();
} }
@ -159,7 +152,6 @@ impl Parser {
} }
fn containertype(&mut self) -> anyhow::Result<ContainerType> { fn containertype(&mut self) -> anyhow::Result<ContainerType> {
println!("containertype");
Ok(if self.check(&LeftParen) { Ok(if self.check(&LeftParen) {
self.advance(); self.advance();
if self.match_token(Group) { if self.match_token(Group) {

View file

@ -1,11 +1,21 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use crate::parse::tokens::{ use crate::parse::tokens::{
Token, Token,
TokenType::{self, *}, TokenType::{self, *},
KEYWORDS,
}; };
static KEYWORDS: Lazy<HashMap<&'static str, TokenType>> = Lazy::new(|| {
HashMap::from([
("structure", Structure),
("styles", Styles),
("group", Group),
("px", Px),
])
});
pub fn scan(vis: &str) -> anyhow::Result<Vec<Token>> { pub fn scan(vis: &str) -> anyhow::Result<Vec<Token>> {
let mut scanner = Scanner::new(vis); let mut scanner = Scanner::new(vis);
scanner.scan(); scanner.scan();
@ -126,10 +136,7 @@ impl<'a> Scanner<'a> {
self.advance(); self.advance();
} }
let text = self.chars[self.start_pos..self.current_pos].concat(); let text = self.chars[self.start_pos..self.current_pos].concat();
let tokentype = KEYWORDS let tokentype = KEYWORDS.get(text.as_str()).cloned().unwrap_or(Identifier);
.get(text.as_str())
.map(|d| *d)
.unwrap_or(Identifier);
self.add_token(tokentype); self.add_token(tokentype);
} }
@ -175,7 +182,7 @@ impl<'a> Scanner<'a> {
return false; return false;
} }
self.current_pos += 1; self.current_pos += 1;
return true; true
} }
fn advance(&mut self) -> &str { fn advance(&mut self) -> &str {
@ -192,18 +199,14 @@ impl<'a> Scanner<'a> {
} }
fn is_digit(char: &str) -> bool { 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 { fn is_alpha(char: &str) -> bool {
if char.len() == 0 { if char.is_empty() {
false false
} else { } else {
let char = char.chars().next().unwrap(); let char = char.chars().next().unwrap();
if char.is_alphabetic() || char == '_' { char.is_alphabetic() || char == '_'
true
} else {
false
}
} }
} }

View file

@ -1,16 +1,3 @@
use std::cell::LazyCell;
use std::collections::HashMap;
use TokenType::*;
pub const KEYWORDS: LazyCell<HashMap<&str, TokenType>> = 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)] #[derive(Debug, Clone)]
pub struct Token { pub struct Token {
pub tokentype: TokenType, pub tokentype: TokenType,

View file

View file

@ -1,10 +1,7 @@
use crate::Vis; use crate::Vis;
pub mod html; pub mod svg_renderer;
mod rendering_svg_elements;
mod svg_renderer;
pub mod svglib; pub mod svglib;
pub mod svgrender2;
/// trait for turning the object model into a byte representation /// trait for turning the object model into a byte representation
pub trait Renderer { pub trait Renderer {

View file

@ -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::<String>().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");
}
}

View file

@ -1,117 +1,134 @@
// use crate::render::svglib::rect::{rect, Rect}; use crate::render::svglib::rect::rect;
// use crate::render::svglib::svg::svg; use crate::render::svglib::svg::{svg, Svg};
// use crate::render::svglib::text::text; use crate::render::svglib::text::text;
// use crate::render::svglib::Value; use crate::render::Renderer;
// use crate::render::Renderer; use crate::Element::Node;
// use crate::{Element, Vis}; use crate::{Element, StyleNode, Vis};
// use std::fs::File;
// use std::io::Write; pub struct SvgRender;
//
// struct SvgRenderer {} impl Renderer for SvgRender {
// impl Renderer for SvgRenderer { fn render(&self, vis: Vis) -> anyhow::Result<Vec<u8>> {
// fn render(&self, vis: Vis) -> anyhow::Result<Vec<u8>> { let mut svg = svg();
// let style = include_str!("svg_node.css");
// let mut svg = svg(); svg.width(100);
// svg.viewbox("0, 0, 300, 300"); svg.height(100);
// svg.style(style); let style = include_str!("svg_node.css");
// svg.style(style);
// let current_x = start_x + padding; let (w, h) = render_elements(&mut svg, &vis.structure, &vis.styles, 0, 0);
// let current_y = start_y + padding; svg.viewbox(format!("0 0 {} {}", w, h));
// let mut max_width: f32 = 0.0; Ok(svg.to_string().into_bytes())
// let mut max_height: f32 = 0.0; }
// }
// let grid_cols = (elements.len() as f32).sqrt().ceil() as usize;
// fn render_elements(
// for e in vis.structure { svg: &mut Svg,
// let col = i % grid_cols; elements: &Vec<Element>,
// let row = i / grid_cols; _styles: &Vec<StyleNode>,
// start_x: u32,
// let child_x = current_x + col as f32 * (grid_cell_size + spacing); start_y: u32,
// let child_y = current_y + row as f32 * (grid_cell_size + spacing); ) -> (u32, u32) {
// let padding = 15;
// if let Element::Node(_, label, children) = e {
// if let Some(label) = label { let mut total_width = 0;
// let mut longest_len = 0; let mut total_height = 0;
// for line in label.lines() { let mut x = start_x;
// longest_len = longest_len.max(line.len()); let y = start_y;
// }
// let width = longest_len / 2; for element in elements {
// let linecount = label.lines().count(); if let Node(id, label, children) = element {
// let height = linecount + 1; let (width, height) = if !children.is_empty() {
// let x = 1; render_elements(svg, children, _styles, x + padding, y + padding)
// let y = ((height - linecount) / 2 + 2) as f64 } else if let Node(_, Some(label), _) = element {
// - if linecount == 1 { 0.25 } else { 0.0 }; let label_width = label.lines().map(|l| l.len()).max().unwrap_or(0) as u32;
// let label_height = label.lines().count() as u32;
// let width = label.len() * 10; ((label_width as f32 * 4.9 + 8.0) as u32, label_height * 15)
// let height = label.lines().collect::<String>().len() * 10; } else {
// svg.add( (0, 0)
// rect() };
// .x(child_x)
// .y(child_y) total_width += width + padding;
// .width(width) total_height = u32::max(total_height, height + padding);
// .height(height)
// .fill("none") svg.add(
// .stroke("green"), rect()
// ); .id(id)
// .x(x + padding)
// if let Some(label) = label { .y(y + padding)
// svg.add( .width(width)
// text() .height(height)
// .x(child_x + (width as f32 / 2.0)) .stroke("white")
// .y(child_y + (height as f32 / 2.0)) .fill("none"),
// .text(label) );
// .attr("text-anchor", "middle"),
// ); svg.add(
// } text()
// .x(x + padding + width / 2)
// svg.add(text); .y(start_y + padding)
// } else { .fill("white")
// svg.add(rectangle(0, 0, 70, 30)); .text(label.as_ref().map(|l| l.as_str()).unwrap_or(""))
// } .attr("text-anchor", "middle")
// } .attr("stroke-width", "1")
// } .class("node"),
// );
// let svg = svg.to_string(); x += width + padding;
// }
// let mut file = File::create("output.svg")?; }
// file.write_all(svg.as_bytes())?;
// Ok(svg.as_bytes().to_vec()) (total_width + padding, total_height + padding)
// } }
// }
// #[cfg(test)]
// fn rectangle<V>(x: usize, y: usize, width: V, height: V) -> Rect mod tests {
// where use super::*;
// V: Into<Value>, use crate::{ContainerType, StyleNode, Vis};
// { use std::fs::File;
// Rect::new() use std::io::Write;
// .x(x)
// .y(y) #[test]
// .width(width) fn test_render_vis_with_grid_layout() {
// .height(height) // Create a mock StyleNode
// .class("rect") let style_node = StyleNode {
// .fill("none") id_ref: "node_1".to_string(),
// } containertype: ContainerType::NonGroup,
// attributes: [("fill".to_string(), "white".to_string())]
// #[cfg(test)] .iter()
// mod tests { .cloned()
// use super::*; .collect(),
// use crate::Element; };
// use std::fs::File;
// use std::io::Write; // Create mock Elements
// let element1 = Node(
// #[test] "node1".to_string(),
// fn test_render() { Some("Node 1\nlonger".to_string()),
// let vis = Vis { vec![], // No child elements
// structure: vec![Element::new_node( );
// "id", let element2 = Node(
// Some("blokkendoos\nkoepeon\nknallen\nhond"), "node2".to_string(),
// vec![], Some("Node 2".to_string()),
// )], vec![], // No child elements
// styles: vec![], );
// };
// let renderer = SvgRenderer {}; let element3 = Node(
// let buf = renderer.render(vis).unwrap(); "node3".to_string(),
// let mut file = File::create("output.svg").expect("Unable to create file"); Some("Node 3\nis longer\nthan you are to me".to_string()),
// file.write_all(&buf).expect("Unable to write data"); 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");
}
}

View file

@ -1,16 +1,13 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Shape, Value}; use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Shape, Value};
pub fn circle() -> Circle { pub fn circle() -> Circle {
Circle::new() Circle::default()
} }
#[derive(Debug, Default, Clone)]
pub struct Circle(Vec<Att>); pub struct Circle(Vec<Att>);
impl Circle { impl Circle {
pub fn new() -> Self {
Self(vec![])
}
pub fn id<V: Into<Value>>(mut self, id: V) -> Self { pub fn id<V: Into<Value>>(mut self, id: V) -> Self {
self.0.push(att("id", id)); self.0.push(att("id", id));
self self

View file

@ -1,22 +1,15 @@
use crate::render::svglib::{att, att_to_string, Att, Value}; use crate::render::svglib::{att, att_to_string, Att, Value};
use std::fmt;
pub fn div() -> Div { pub fn div() -> Div {
Div::new() Div::default()
} }
#[derive(Debug, Clone, Default)]
pub struct Div { pub struct Div {
atts: Vec<Att>, atts: Vec<Att>,
child: String, child: String,
} }
impl Div { impl Div {
pub fn new() -> Self {
Self {
atts: vec![],
child: "".to_string(),
}
}
pub fn id<V: Into<Value>>(&mut self, id: V) { pub fn id<V: Into<Value>>(&mut self, id: V) {
self.atts.push(att("id", id)); self.atts.push(att("id", id));
} }
@ -34,15 +27,18 @@ impl Div {
self self
} }
pub fn to_string(&self) -> String { pub fn atts(&self) -> &[Att] {
format!( &self.atts
}
}
impl fmt::Display for Div {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
r#"<div xmlns="http://www.w3.org/1999/xhtml"{}>{}</div>"#, r#"<div xmlns="http://www.w3.org/1999/xhtml"{}>{}</div>"#,
self.atts.iter().map(att_to_string).collect::<String>(), self.atts.iter().map(att_to_string).collect::<String>(),
self.child self.child
) )
} }
fn atts(&self) -> &[Att] {
&self.atts
}
} }

View file

@ -2,22 +2,16 @@ use crate::render::svglib::div::Div;
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
pub fn foreign_object() -> ForeignObject { pub fn foreign_object() -> ForeignObject {
ForeignObject::new() ForeignObject::default()
} }
#[derive(Default, Debug, Clone)]
pub struct ForeignObject { pub struct ForeignObject {
child: Option<Div>, //for now child: Option<Div>, //for now
atts: Vec<Att>, atts: Vec<Att>,
} }
impl ForeignObject { impl ForeignObject {
pub fn new() -> Self {
Self {
child: None,
atts: vec![],
}
}
pub fn id<V>(&mut self, id: V) pub fn id<V>(&mut self, id: V)
where where
V: Into<Value>, V: Into<Value>,
@ -47,7 +41,7 @@ impl ForeignObject {
self self
} }
pub fn add(mut self, child: Div) -> Self { pub fn add_child(mut self, child: Div) -> Self {
self.child = Some(child); self.child = Some(child);
self self
} }
@ -65,10 +59,7 @@ impl Element for ForeignObject {
fn to_string(&self) -> String { fn to_string(&self) -> String {
format!( format!(
r#"<foreignObject{}>{}</foreignObject>"#, r#"<foreignObject{}>{}</foreignObject>"#,
self.atts self.atts.iter().map(att_to_string).collect::<String>(),
.iter()
.map(|a| att_to_string(a))
.collect::<String>(),
self.child self.child
.as_ref() .as_ref()
.map(|c| c.to_string()) .map(|c| c.to_string())

View file

@ -1,18 +1,15 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
pub fn image() -> Image { pub fn image() -> Image {
Image::new() Image::default()
} }
#[derive(Default)]
pub struct Image { pub struct Image {
atts: Vec<Att>, atts: Vec<Att>,
} }
impl Image { impl Image {
pub fn new() -> Self {
Self { atts: vec![] }
}
pub fn id<V: Into<Value>>(&mut self, id: V) { pub fn id<V: Into<Value>>(&mut self, id: V) {
self.atts.push(att("id", id)); self.atts.push(att("id", id));
} }

View file

@ -1,9 +1,7 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
pub fn link(href: &str) -> Link { pub fn link(href: &str) -> Link {
let mut atts = vec![]; Link(vec![att("href", href)])
atts.push(att("href", href));
Link(atts)
} }
pub struct Link(Vec<Att>); pub struct Link(Vec<Att>);

View file

@ -1,3 +1,5 @@
use std::fmt::Display;
pub mod circle; pub mod circle;
pub mod div; pub mod div;
pub mod ellipse; pub mod ellipse;
@ -20,9 +22,9 @@ impl From<&str> for Value {
} }
} }
impl Value { impl Display for Value {
pub fn to_string(&self) -> String { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.clone() write!(f, "{}", self.0)
} }
} }
@ -94,7 +96,7 @@ pub trait Shape {
V: Into<Value>; V: Into<Value>;
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Att { pub struct Att {
name: String, name: String,
value: Value, value: Value,
@ -117,12 +119,5 @@ where
} }
fn att_to_string(att: &Att) -> String { fn att_to_string(att: &Att) -> String {
format!(r#" {}="{}""#, att.name, att.value.to_string()) format!(r#" {}="{}""#, att.name, att.value)
}
fn att_str2(att_name: &str, att_val: &Option<String>) -> String {
att_val
.as_ref()
.map(|val| format!(r#" {}="{}""#, att_name, val))
.unwrap_or("".to_string())
} }

View file

@ -31,10 +31,7 @@ impl Path {
} }
} }
fn id<V>(&mut self, id: V) pub fn id<V: Into<Value>>(&mut self, id: V) {
where
V: Into<Value>,
{
self.atts.push(att("id", id)); self.atts.push(att("id", id));
} }

View file

@ -1,17 +1,15 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
pub fn rect() -> Rect { pub fn rect() -> Rect {
Rect::new() Rect::default()
} }
#[derive(Debug, Clone, Default)]
pub struct Rect { pub struct Rect {
atts: Vec<Att>, atts: Vec<Att>,
} }
impl Rect { impl Rect {
pub fn new() -> Self {
Self { atts: vec![] }
}
pub fn id<V: Into<Value>>(mut self, id: V) -> Self { pub fn id<V: Into<Value>>(mut self, id: V) -> Self {
self.atts.push(att("id", id)); self.atts.push(att("id", id));
self self

View file

@ -1,9 +1,11 @@
use crate::render::svglib::{att, att_to_string, Att, Element, Value}; use crate::render::svglib::{att, att_to_string, Att, Element, Value};
use std::fmt;
pub fn svg() -> Svg { pub fn svg() -> Svg {
Svg::new() Svg::default()
} }
#[derive(Default)]
pub struct Svg { pub struct Svg {
style: Option<String>, style: Option<String>,
elements: Vec<Box<dyn Element>>, elements: Vec<Box<dyn Element>>,
@ -11,14 +13,6 @@ pub struct Svg {
} }
impl Svg { impl Svg {
pub fn new() -> Self {
Self {
style: None,
elements: vec![],
atts: vec![],
}
}
pub fn style(&mut self, style: &str) { pub fn style(&mut self, style: &str) {
self.style = Some(style.to_string()); self.style = Some(style.to_string());
} }
@ -47,26 +41,24 @@ impl Svg {
pub fn transform<V: Into<Value>>(&mut self, transform: V) { pub fn transform<V: Into<Value>>(&mut self, transform: V) {
self.atts.push(att("transform", transform)); self.atts.push(att("transform", transform));
} }
}
pub fn to_string(&self) -> String { impl fmt::Display for Svg {
let mut svg = String::new(); fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
svg.push_str( write!(
format!( f,
r#"<svg xmlns="http://www.w3.org/2000/svg" {}>{}"#, r#"<svg xmlns="http://www.w3.org/2000/svg" {}>{}"#,
self.atts.iter().map(att_to_string).collect::<String>(), self.atts.iter().map(att_to_string).collect::<String>(),
self.style self.style
.as_ref() .as_ref()
.map(|s| format!("<style>{}</style>", s.to_string())) .map(|s| format!("<style>{}</style>", s))
.unwrap_or("".to_string()) .unwrap_or("".to_string())
) )?;
.as_str(),
);
for e in &self.elements { 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, "</svg>")
svg
} }
} }
@ -77,7 +69,7 @@ mod tests {
#[test] #[test]
fn style() { fn style() {
let mut svg = Svg::new(); let mut svg = svg();
svg.style(".id { background-color: red; }"); svg.style(".id { background-color: red; }");
assert_eq!( assert_eq!(
r#"<svg xmlns="http://www.w3.org/2000/svg"><style>.id { background-color: red; }</style></svg>"#, r#"<svg xmlns="http://www.w3.org/2000/svg"><style>.id { background-color: red; }</style></svg>"#,
@ -87,7 +79,7 @@ mod tests {
#[test] #[test]
fn add_rect() { fn add_rect() {
let mut svg = Svg::new(); let mut svg = svg();
svg.preserveaspectratio("none"); svg.preserveaspectratio("none");
svg.add(rect().x(0).y(0).width(10).height(10)); svg.add(rect().x(0).y(0).width(10).height(10));
assert_eq!( assert_eq!(

View file

@ -1,7 +1,8 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value}; use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
use std::fmt::Write;
pub fn text() -> Text { pub fn text() -> Text {
Text::new() Text::default()
} }
pub struct Text { pub struct Text {
@ -9,14 +10,16 @@ pub struct Text {
text: String, text: String,
} }
impl Text { impl Default for Text {
pub fn new() -> Self { fn default() -> Self {
Self { Self {
atts: vec![], atts: vec![],
text: "".to_string(), text: "".to_string(),
} }
} }
}
impl Text {
pub fn text<V: Into<String>>(mut self, text: V) -> Self { pub fn text<V: Into<String>>(mut self, text: V) -> Self {
self.text = text.into(); self.text = text.into();
self self
@ -77,15 +80,17 @@ impl Element for Text {
} }
fn to_string(&self) -> String { fn to_string(&self) -> String {
let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect(); // let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect();
let x: String = x[0].value.to_string(); // let x: String = x[0].value.to_string();
format!( format!(
r#"<text{}>{}</text>"#, r#"<text{}>{}</text>"#,
self.atts.iter().map(att_to_string).collect::<String>(), self.atts.iter().map(att_to_string).collect::<String>(),
self.text self.text.lines().fold(String::new(), |mut output, l| {
.lines() let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect();
.map(|l| format!("<tspan x=\"{}\" dy=\"10\">{}</tspan>", x, l)) let x: String = x[0].value.to_string();
.collect::<String>(), let _ = write!(output, "<tspan x=\"{}\" dy=\"10\">{}</tspan>", x, l);
output
})
) )
} }

View file

@ -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<Vec<u8>> {
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<Element>,
_styles: &Vec<StyleNode>,
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");
}
}