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"
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",
]

View file

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

View file

@ -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<String>,
) -> 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)
}
}
}

View file

@ -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");

View file

@ -105,7 +105,6 @@ impl Parser {
}
fn styles(&mut self) -> anyhow::Result<Vec<StyleNode>> {
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<StyleNode> {
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<HashMap<String, String>> {
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<ContainerType> {
println!("containertype");
Ok(if self.check(&LeftParen) {
self.advance();
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 crate::parse::tokens::{
Token,
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>> {
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 == '_'
}
}

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)]
pub struct Token {
pub tokentype: TokenType,

View file

View file

@ -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 {

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::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<Vec<u8>> {
// 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::<String>().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<V>(x: usize, y: usize, width: V, height: V) -> Rect
// where
// V: Into<Value>,
// {
// 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<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;
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");
}
}

View file

@ -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<Att>);
impl Circle {
pub fn new() -> Self {
Self(vec![])
}
pub fn id<V: Into<Value>>(mut self, id: V) -> Self {
self.0.push(att("id", id));
self

View file

@ -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<Att>,
child: String,
}
impl Div {
pub fn new() -> Self {
Self {
atts: vec![],
child: "".to_string(),
}
}
pub fn id<V: Into<Value>>(&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#"<div xmlns="http://www.w3.org/1999/xhtml"{}>{}</div>"#,
self.atts.iter().map(att_to_string).collect::<String>(),
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};
pub fn foreign_object() -> ForeignObject {
ForeignObject::new()
ForeignObject::default()
}
#[derive(Default, Debug, Clone)]
pub struct ForeignObject {
child: Option<Div>, //for now
atts: Vec<Att>,
}
impl ForeignObject {
pub fn new() -> Self {
Self {
child: None,
atts: vec![],
}
}
pub fn id<V>(&mut self, id: V)
where
V: Into<Value>,
@ -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#"<foreignObject{}>{}</foreignObject>"#,
self.atts
.iter()
.map(|a| att_to_string(a))
.collect::<String>(),
self.atts.iter().map(att_to_string).collect::<String>(),
self.child
.as_ref()
.map(|c| c.to_string())

View file

@ -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<Att>,
}
impl Image {
pub fn new() -> Self {
Self { atts: vec![] }
}
pub fn id<V: Into<Value>>(&mut self, id: V) {
self.atts.push(att("id", id));
}

View file

@ -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<Att>);

View file

@ -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<Value>;
}
#[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>) -> String {
att_val
.as_ref()
.map(|val| format!(r#" {}="{}""#, att_name, val))
.unwrap_or("".to_string())
format!(r#" {}="{}""#, att.name, att.value)
}

View file

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

View file

@ -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<Att>,
}
impl Rect {
pub fn new() -> Self {
Self { atts: vec![] }
}
pub fn id<V: Into<Value>>(mut self, id: V) -> Self {
self.atts.push(att("id", id));
self

View file

@ -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<String>,
elements: Vec<Box<dyn Element>>,
@ -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<V: Into<Value>>(&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!(
impl fmt::Display for Svg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
r#"<svg xmlns="http://www.w3.org/2000/svg" {}>{}"#,
self.atts.iter().map(att_to_string).collect::<String>(),
self.style
.as_ref()
.map(|s| format!("<style>{}</style>", s.to_string()))
.map(|s| format!("<style>{}</style>", s))
.unwrap_or("".to_string())
)
.as_str(),
);
)?;
for e in &self.elements {
svg.push_str(e.to_string().as_str());
write!(f, "{}", e.to_string().as_str())?;
}
svg.push_str("</svg>");
svg
write!(f, "</svg>")
}
}
@ -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#"<svg xmlns="http://www.w3.org/2000/svg"><style>.id { background-color: red; }</style></svg>"#,
@ -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!(

View file

@ -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<V: Into<String>>(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#"<text{}>{}</text>"#,
self.atts.iter().map(att_to_string).collect::<String>(),
self.text
.lines()
.map(|l| format!("<tspan x=\"{}\" dy=\"10\">{}</tspan>", x, l))
.collect::<String>(),
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, "<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");
}
}