From 25c3d008cc6b022f2a4c34eaba9cc55ed715291c Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sun, 19 Jan 2025 21:34:48 +0100 Subject: [PATCH] parsed styles and struggling with svg --- Cargo.toml | 2 +- examples/bank_architecture/bank.vis | 30 +---- src/lib.rs | 18 ++- src/parse/parser.rs | 78 ++++++++++++- src/parse/scanner.rs | 6 +- src/render/html.rs | 0 src/render/mod.rs | 12 ++ src/render/rendering_svg_elements.rs | 162 +++++++++++++++++++++++++++ src/render/svg_node.css | 14 +++ src/render/svg_renderer.rs | 117 +++++++++++++++++++ src/render/svglib/circle.rs | 78 +++++++++++++ src/render/svglib/div.rs | 51 +++++++++ src/render/svglib/ellipse.rs | 85 ++++++++++++++ src/render/svglib/foreign_object.rs | 75 +++++++++++++ src/render/svglib/group.rs | 70 ++++++++++++ src/render/svglib/image.rs | 85 ++++++++++++++ src/render/svglib/line.rs | 80 +++++++++++++ src/render/svglib/link.rs | 34 ++++++ src/render/svglib/path.rs | 124 ++++++++++++++++++++ src/render/svglib/rect.rs | 91 +++++++++++++++ src/render/svglib/svg.rs | 111 ++++++++++++++++++ src/render/svglib/text.rs | 105 +++++++++++++++++ src/render/svgrender2.rs | 10 ++ 23 files changed, 1404 insertions(+), 34 deletions(-) create mode 100644 src/render/html.rs create mode 100644 src/render/rendering_svg_elements.rs create mode 100644 src/render/svg_node.css create mode 100644 src/render/svg_renderer.rs create mode 100644 src/render/svglib/circle.rs create mode 100644 src/render/svglib/div.rs create mode 100644 src/render/svglib/ellipse.rs create mode 100644 src/render/svglib/foreign_object.rs create mode 100644 src/render/svglib/group.rs create mode 100644 src/render/svglib/image.rs create mode 100644 src/render/svglib/line.rs create mode 100644 src/render/svglib/link.rs create mode 100644 src/render/svglib/path.rs create mode 100644 src/render/svglib/rect.rs create mode 100644 src/render/svglib/svg.rs create mode 100644 src/render/svglib/text.rs create mode 100644 src/render/svgrender2.rs diff --git a/Cargo.toml b/Cargo.toml index ad436f1..0b62bf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" [dependencies] anyhow = "1.0" -unicode-segmentation = "1.1" +unicode-segmentation = "1.1" \ No newline at end of file diff --git a/examples/bank_architecture/bank.vis b/examples/bank_architecture/bank.vis index 2889de1..fb33805 100644 --- a/examples/bank_architecture/bank.vis +++ b/examples/bank_architecture/bank.vis @@ -21,10 +21,10 @@ structure { interest_engine: "InterestEngine" } } - bank-->calc - bank_scripts--<>bank_db - bank_motor--<>bank_db - interest_engine-->calc + bank==>calc + bank_scripts==<>bank_db + bank_motor==<>bank_db + interest_engine==>calc } styles { @@ -32,25 +32,7 @@ styles { type: textnode orientation: horizontal shape: rectangle - font-family: arial - border-width: 1px - border-color: gray - } - functions(group) { - background-color: yellow - font-family: arial - border-radius: 20px - border-width: 1px - border-color: gray - } - systems(group) { - background-color: lightblue - } - tag1: "⚭" { // how will this work? - right:0px - top:0px - } - tag2: { - itchy: scratchy } + functions(group) {} + systems(group) {} } diff --git a/src/lib.rs b/src/lib.rs index 5c0442c..c88079b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,14 +16,28 @@ pub enum Element { Edge(String, String, TokenType, Option), } -#[derive(Debug)] +impl Element { + pub fn new_node(id: &str, label: Option<&str>, children: Vec) -> Element { + Element::Node(id.into(), label.map(|s| s.into()), children) + } + pub fn new_edge( + id: String, + source: String, + token: TokenType, + label: Option, + ) -> Element { + Element::Edge(id, source, token, label) + } +} + +#[derive(Debug, Clone)] pub struct StyleNode { pub id_ref: String, pub containertype: ContainerType, pub attributes: HashMap, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ContainerType { Node, Group, diff --git a/src/parse/parser.rs b/src/parse/parser.rs index fd451ff..937394c 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -3,13 +3,14 @@ use crate::{ Token, TokenType::{self, *}, }, - Element, StyleNode, Vis, + ContainerType, Element, StyleNode, Vis, }; use anyhow::anyhow; +use std::collections::HashMap; pub fn parse_vis(contents: &str) -> anyhow::Result { let tokens = crate::parse::scanner::scan(contents)?; - // println!("{:?}", tokens); + println!("{:?}", tokens); let mut parser = Parser::new(tokens); Ok(Vis { @@ -103,7 +104,71 @@ impl Parser { } fn styles(&mut self) -> anyhow::Result> { - Ok(vec![]) + println!("styles"); + if self.match_token(Styles) { + self.consume(LeftBrace, "Expected '{'")?; + let mut styles = vec![]; + while !self.check(&RightBrace) { + styles.push(self.style()?); + } + self.consume(RightBrace, "Expected '}'")?; + Ok(styles) + } else { + Ok(vec![]) + } + } + + 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 '}'")?; + + Ok(StyleNode { + id_ref: idref, + containertype, + attributes, + }) + } else { + Err(anyhow!("Expected identifier"))? + } + } + + 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(); + } + Ok(elements) + } + + fn containertype(&mut self) -> anyhow::Result { + println!("containertype"); + Ok(if self.check(&LeftParen) { + self.advance(); + if self.match_token(Group) { + ContainerType::Group + } else { + ContainerType::Node + } + } else { + ContainerType::Node + }) } fn consume(&mut self, tokentype: TokenType, expect: &str) -> anyhow::Result<&Token> { @@ -111,7 +176,12 @@ impl Parser { if self.check(&tokentype) { Ok(self.advance()) } else { - Err(anyhow!("Error: {} on line {}", expect, current.line)) + Err(anyhow!( + "Error: {} but was '{}' on line {}", + expect, + self.peek().lexeme, + current.line + )) } } diff --git a/src/parse/scanner.rs b/src/parse/scanner.rs index 844146a..9ad014d 100644 --- a/src/parse/scanner.rs +++ b/src/parse/scanner.rs @@ -48,8 +48,8 @@ impl<'a> Scanner<'a> { "," => self.add_token(Comma), "." => self.add_token(Dot), - "-" => { - if self.match_token("-") { + "=" => { + if self.match_token("=") { if self.match_token(">") { self.add_token(ArrowRight); } else if self.match_token("<") { @@ -122,7 +122,7 @@ impl<'a> Scanner<'a> { } fn identifier(&mut self) { - while is_alpha(self.peek()) || is_digit(self.peek()) { + while is_alpha(self.peek()) || is_digit(self.peek()) || self.peek() == "-" { self.advance(); } let text = self.chars[self.start_pos..self.current_pos].concat(); diff --git a/src/render/html.rs b/src/render/html.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/render/mod.rs b/src/render/mod.rs index e69de29..d1cb3f0 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -0,0 +1,12 @@ +use crate::Vis; + +pub mod html; +mod rendering_svg_elements; +mod svg_renderer; +pub mod svglib; +mod svgrender2; + +/// trait for turning the object model into a byte representation +pub trait Renderer { + fn render(&self, vis: Vis) -> anyhow::Result>; +} diff --git a/src/render/rendering_svg_elements.rs b/src/render/rendering_svg_elements.rs new file mode 100644 index 0000000..22f31ce --- /dev/null +++ b/src/render/rendering_svg_elements.rs @@ -0,0 +1,162 @@ +use crate::render::svglib::rect::rect; +use crate::render::svglib::svg::{svg, Svg}; +use crate::render::svglib::text::text; +use crate::{Element, StyleNode, 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(id, 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::Node, + 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_node.css b/src/render/svg_node.css new file mode 100644 index 0000000..7325bd8 --- /dev/null +++ b/src/render/svg_node.css @@ -0,0 +1,14 @@ +svg { + background-color: white; +} + +.node { + border: 1px solid black; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: 1em; + font-size: 10px; + font-family: sans-serif; +} diff --git a/src/render/svg_renderer.rs b/src/render/svg_renderer.rs new file mode 100644 index 0000000..35c1f88 --- /dev/null +++ b/src/render/svg_renderer.rs @@ -0,0 +1,117 @@ +// 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"); +// } +// } diff --git a/src/render/svglib/circle.rs b/src/render/svglib/circle.rs new file mode 100644 index 0000000..ca1e9dd --- /dev/null +++ b/src/render/svglib/circle.rs @@ -0,0 +1,78 @@ +use crate::render::svglib::{att, att_str3, Att, Element, ElementType, Shape, Value}; + +pub fn circle() -> Circle { + Circle::new() +} + +pub struct Circle(Vec); + +impl Circle { + fn new() -> Self { + Self(vec![]) + } + + fn id>(mut self, id: V) -> Self { + self.0.push(att("id", id)); + self + } + fn cx>(mut self, cx: V) -> Self { + self.0.push(att("cx", cx)); + self + } + fn cy>(mut self, cy: V) -> Self { + self.0.push(att("cy", cy)); + self + } + fn r>(mut self, r: V) -> Self { + self.0.push(att("r", r)); + self + } +} + +impl Shape for Circle { + fn fill>(mut self, value: V) -> Self { + self.0.push(att("fill", value)); + self + } + + fn stroke>(mut self, value: V) -> Self { + self.0.push(att("stroke", value)); + self + } + + fn transform>(mut self, value: V) -> Self { + self.0.push(att("transform", value)); + self + } +} + +impl Element for Circle { + fn get_type(&self) -> ElementType { + ElementType::Circle + } + + fn atts(&self) -> &[Att] { + &self.0 + } + + fn to_string(&self) -> String { + format!( + r#""#, + self.0.iter().map(att_str3).collect::() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_circle() { + let circle = circle().cx("1em").cy(0).r("10").id("c"); + assert_eq!( + r#""#, + circle.to_string() + ) + } +} diff --git a/src/render/svglib/div.rs b/src/render/svglib/div.rs new file mode 100644 index 0000000..02c31fa --- /dev/null +++ b/src/render/svglib/div.rs @@ -0,0 +1,51 @@ +use crate::render::svglib::{att, att_str3, Att, Value}; + +pub fn div() -> Div { + Div::new() +} + +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) + where + V: Into, + { + self.atts.push(att("id", id)); + } + + pub fn class(mut self, class: V) -> Self + where + V: Into, + { + self.atts.push(att("class", class)); + self + } + + pub fn innerHTML>(mut self, html: V) -> Self { + self.child = html.into(); + self + } + + pub fn to_string(&self) -> String { + format!( + r#"
{}
"#, + self.atts.iter().map(att_str3).collect::(), + self.child + ) + } + + fn atts(&self) -> &[Att] { + &self.atts + } +} diff --git a/src/render/svglib/ellipse.rs b/src/render/svglib/ellipse.rs new file mode 100644 index 0000000..4271395 --- /dev/null +++ b/src/render/svglib/ellipse.rs @@ -0,0 +1,85 @@ +use crate::render::svglib::{att, att_str3, Att, Element, ElementType, Shape, Value}; + +pub fn ellipse() -> Ellipse { + Ellipse(vec![]) +} + +#[derive(Debug)] +pub struct Ellipse(Vec); + +impl Ellipse { + fn cx>(mut self, cx: V) -> Self { + self.0.push(att("cx", cx)); + self + } + fn cy>(mut self, cy: V) -> Self { + self.0.push(att("cy", cy)); + self + } + fn rx>(mut self, rx: V) -> Self { + self.0.push(att("rx", rx)); + self + } + fn ry>(mut self, ry: V) -> Self { + self.0.push(att("ry", ry)); + self + } +} + +impl Shape for Ellipse { + fn fill(mut self, value: V) -> Self + where + V: Into, + { + self.0.push(att("fill", value)); + self + } + + fn stroke(mut self, value: V) -> Self + where + V: Into, + { + self.0.push(att("stroke", value)); + self + } + + fn transform(mut self, value: V) -> Self + where + V: Into, + { + self.0.push(att("transform", value)); + self + } +} + +impl Element for Ellipse { + fn get_type(&self) -> ElementType { + ElementType::Ellipse + } + + fn atts(&self) -> &[Att] { + &self.0 + } + + fn to_string(&self) -> String { + format!( + r#""#, + self.0.iter().map(att_str3).collect::() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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/foreign_object.rs b/src/render/svglib/foreign_object.rs new file mode 100644 index 0000000..922e06b --- /dev/null +++ b/src/render/svglib/foreign_object.rs @@ -0,0 +1,75 @@ +use crate::render::svglib::div::Div; +use crate::render::svglib::{att, att_str3, Att, Element, ElementType, Value}; + +pub fn foreign_object() -> ForeignObject { + ForeignObject::new() +} + +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, + { + self.atts.push(att("id", id)); + } + + pub fn x>(mut self, x: V) -> Self { + self.atts.push(att("x", x)); + self + } + + pub fn y>(mut self, y: V) -> Self { + self.atts.push(att("y", y)); + self + } + pub fn width>(mut self, width: V) -> Self { + self.atts.push(att("width", width)); + self + } + pub fn height>(mut self, height: V) -> Self { + self.atts.push(att("height", height)); + self + } + pub fn class>(mut self, class: V) -> Self { + self.atts.push(att("class", class)); + self + } + + pub fn add(mut self, child: Div) -> Self { + self.child = Some(child); + self + } +} + +impl Element for ForeignObject { + fn get_type(&self) -> ElementType { + ElementType::ForeignObject + } + + fn atts(&self) -> &[Att] { + &self.atts + } + + fn to_string(&self) -> String { + format!( + r#"{}"#, + self.atts.iter().map(|a| att_str3(a)).collect::(), + self.child + .as_ref() + .map(|c| c.to_string()) + .unwrap_or("".to_string()), + ) + } +} diff --git a/src/render/svglib/group.rs b/src/render/svglib/group.rs new file mode 100644 index 0000000..498c084 --- /dev/null +++ b/src/render/svglib/group.rs @@ -0,0 +1,70 @@ +use crate::render::svglib::{att, att_str3, Att, Element, ElementType, Value}; + +pub fn group() -> Group { + Group { + children: Vec::new(), + atts: vec![], + } +} + +pub struct Group { + children: Vec>, + atts: Vec, +} + +impl Group { + pub fn add(&mut self, child: impl Element + 'static) { + self.children.push(Box::new(child)); + } + + pub fn id(&mut self, id: V) + where + V: Into, + { + self.atts.push(att("id", id)); + } + + pub fn transform(&mut self, value: V) + where + V: Into, + { + self.atts.push(att("transform", value)); + } +} + +impl Element for Group { + fn get_type(&self) -> ElementType { + ElementType::Group(Vec::new()) + } + + fn atts(&self) -> &[Att] { + &self.atts + } + + fn to_string(&self) -> String { + let mut svg = format!("", self.atts.iter().map(att_str3).collect::()); + + for e in &self.children { + svg.push_str(e.to_string().as_str()); + } + svg.push_str(""); + svg + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::render::svglib::rect::rect; + + #[test] + fn test_group() { + let mut g = group(); + g.id("testgroup"); + g.add(rect().x(0).y(0).width(10).height(10)); + assert_eq!( + r#""#, + g.to_string() + ) + } +} diff --git a/src/render/svglib/image.rs b/src/render/svglib/image.rs new file mode 100644 index 0000000..6f9da1f --- /dev/null +++ b/src/render/svglib/image.rs @@ -0,0 +1,85 @@ +use crate::render::svglib::{att, att_str3, Att, Element, ElementType, Value}; + +pub fn image() -> Image { + Image::new() +} + +struct Image { + atts: Vec, +} + +impl Image { + fn new() -> Self { + Self { atts: vec![] } + } + + fn id(&mut self, id: V) + where + V: Into, + { + self.atts.push(att("id", id)); + } + + fn transform(&mut self, value: V) + where + V: Into, + { + self.atts.push(att("transform", value)); + } + fn x>(mut self, x: V) -> Self { + self.atts.push(att("x", x)); + self + } + fn y>(mut self, y: V) -> Self { + self.atts.push(att("y", y)); + self + } + fn width>(mut self, width: V) -> Self { + self.atts.push(att("width", width)); + self + } + fn height>(mut self, height: V) -> Self { + self.atts.push(att("height", height)); + self + } + fn href>(mut self, href: V) -> Self { + self.atts.push(att("href", href)); + self + } +} + +impl Element for Image { + fn get_type(&self) -> ElementType { + ElementType::Image + } + + fn atts(&self) -> &[Att] { + &self.atts + } + + fn to_string(&self) -> String { + format!( + r#""#, + self.atts.iter().map(att_str3).collect::() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_image() { + let rect = image() + .href("http://acme.com/coyote.jpg") + .x(0) + .y(0) + .width(10) + .height(10); + assert_eq!( + r#""#, + rect.to_string() + ) + } +} diff --git a/src/render/svglib/line.rs b/src/render/svglib/line.rs new file mode 100644 index 0000000..1f5e030 --- /dev/null +++ b/src/render/svglib/line.rs @@ -0,0 +1,80 @@ +use crate::render::svglib::{att, att_str3, Att, Element, ElementType, Shape, Value}; + +pub fn line() -> Line { + Line(vec![]) +} + +pub struct Line(Vec); + +impl Line { + fn id(&mut self, id: V) + where + V: Into, + { + self.0.push(att("id", id)); + } + + pub fn x1>(mut self, x: V) -> Self { + self.0.push(att("x1", x)); + self + } + pub fn y1>(mut self, y: V) -> Self { + self.0.push(att("y1", y)); + self + } + pub fn x2>(mut self, x2: V) -> Self { + self.0.push(att("x2", x2)); + self + } + pub fn y2>(mut self, y2: V) -> Self { + self.0.push(att("y2", y2)); + self + } + pub fn attr>(mut self, key: &str, value: V) -> Self { + self.0.push(att(key, value)); + self + } +} + +impl Element for Line { + fn get_type(&self) -> ElementType { + ElementType::Line + } + + fn atts(&self) -> &[Att] { + &self.0 + } + + fn to_string(&self) -> String { + format!( + r#" "#, + self.0.iter().map(att_str3).collect::() + ) + } +} + +impl Shape for Line { + fn fill(mut self, value: V) -> Self + where + V: Into, + { + self.0.push(att("fill", value)); + self + } + + fn stroke(mut self, value: V) -> Self + where + V: Into, + { + self.0.push(att("stroke", value)); + self + } + + fn transform(mut self, value: V) -> Self + where + V: Into, + { + self.0.push(att("transform", value)); + self + } +} diff --git a/src/render/svglib/link.rs b/src/render/svglib/link.rs new file mode 100644 index 0000000..c12840d --- /dev/null +++ b/src/render/svglib/link.rs @@ -0,0 +1,34 @@ +use crate::render::svglib::{att, Att, Element, ElementType, Value}; + +pub fn link(href: &str) -> Link { + let mut atts = vec![]; + atts.push(att("href", href)); + Link { atts } +} + +struct Link { + atts: Vec, +} + +impl Link { + fn id(&mut self, id: V) + where + V: Into, + { + self.atts.push(att("id", id)); + } +} + +impl Element for Link { + fn get_type(&self) -> ElementType { + ElementType::Link + } + + fn atts(&self) -> &[Att] { + &self.atts + } + + fn to_string(&self) -> String { + todo!() + } +} diff --git a/src/render/svglib/path.rs b/src/render/svglib/path.rs new file mode 100644 index 0000000..a2ab851 --- /dev/null +++ b/src/render/svglib/path.rs @@ -0,0 +1,124 @@ +use crate::render::svglib::{att, Att, Element, ElementType, Shape, Value}; + +pub fn path(d: &str) -> Path { + Path::new(d) +} + +pub struct Path { + d: String, + atts: Vec, +} + +impl Element for Path { + fn get_type(&self) -> ElementType { + ElementType::Path + } + + fn atts(&self) -> &[Att] { + &self.atts + } + + fn to_string(&self) -> String { + todo!() + } +} + +impl Path { + pub fn new(d: &str) -> Self { + Self { + d: d.to_string(), + atts: vec![], + } + } + + fn id(&mut self, id: V) + where + V: Into, + { + self.atts.push(att("id", id)); + } + + pub fn m(&mut self, x: usize, y: usize) { + self.d.push_str(&format!(" m{} {}", x, y)); + } + + #[allow(non_snake_case)] + pub fn M(&mut self, x: usize, y: usize) { + self.d.push_str(&format!(" M{} {}", x, y)); + } + + pub fn z(&mut self) { + self.d.push_str(" z"); + } + + pub fn l(&mut self, x: usize, y: usize) { + self.d.push_str(&format!(" l{} {}", x, y)); + } + + #[allow(non_snake_case)] + pub fn L(&mut self, x: usize, y: usize) { + self.d.push_str(&format!(" L{} {}", x, y)); + } + + pub fn h(&mut self, x: usize) { + self.d.push_str(&format!(" h{}", x)); + } + + #[allow(non_snake_case)] + pub fn H(&mut self, x: usize) { + self.d.push_str(&format!(" H{}", x)); + } + + pub fn v(&mut self, x: usize) { + self.d.push_str(&format!(" v{}", x)); + } + + #[allow(non_snake_case)] + pub fn V(&mut self, x: usize) { + self.d.push_str(&format!(" V{}", x)); + } + + pub fn c(&mut self, x1: usize, y1: usize, x2: usize, y2: usize) { + self.d.push_str(&format!(" c{} {} {} {}", x1, y1, x2, y2)); + } + + #[allow(non_snake_case)] + pub fn C(&mut self, x1: usize, y1: usize, x2: usize, y2: usize) { + self.d.push_str(&format!(" C{} {} {} {}", x1, y1, x2, y2)); + } + + pub fn s(&mut self, x1: usize, y1: usize, x2: usize, y2: usize) { + self.d.push_str(&format!(" s{} {} {} {}", x1, y1, x2, y2)); + } + + #[allow(non_snake_case)] + pub fn S(&mut self, x1: usize, y1: usize, x2: usize, y2: usize) { + self.d.push_str(&format!(" S{} {} {} {}", x1, y1, x2, y2)); + } +} + +impl Shape for Path { + fn fill(mut self, value: V) -> Self + where + V: Into, + { + self.atts.push(att("fill", value)); + self + } + + fn stroke(mut self, value: V) -> Self + where + V: Into, + { + self.atts.push(att("stroke", value)); + self + } + + fn transform(mut self, value: V) -> Self + where + V: Into, + { + self.atts.push(att("transform", value)); + self + } +} diff --git a/src/render/svglib/rect.rs b/src/render/svglib/rect.rs new file mode 100644 index 0000000..e9d0949 --- /dev/null +++ b/src/render/svglib/rect.rs @@ -0,0 +1,91 @@ +use crate::render::svglib::{att, att_str3, Att, Element, ElementType, Value}; + +pub fn rect() -> Rect { + Rect::new() +} + +pub struct Rect { + atts: Vec, +} + +impl Rect { + pub fn new() -> Self { + let mut atts = vec![]; + Self { atts } + } + pub fn rounded() -> Self { + Self { atts: vec![] } + } + + pub fn x>(mut self, x: V) -> Self { + self.atts.push(att("x", x)); + self + } + pub fn y>(mut self, y: V) -> Self { + self.atts.push(att("y", y)); + self + } + pub fn width>(mut self, width: V) -> Self { + self.atts.push(att("width", width)); + self + } + pub fn height>(mut self, height: V) -> Self { + self.atts.push(att("height", height)); + self + } + pub fn class>(mut self, class: V) -> Self { + self.atts.push(att("class", class)); + self + } + + pub fn fill>(mut self, value: V) -> Self { + self.atts.push(att("fill", value)); + self + } + + pub fn stroke>(mut self, value: V) -> Self { + self.atts.push(att("stroke", value)); + self + } + + pub fn transform>(mut self, value: V) -> Self { + self.atts.push(att("transform", value)); + self + } + + pub fn attr>(mut self, key: &str, value: V) -> Self { + self.atts.push(att(key, value)); + self + } +} + +impl Element for Rect { + fn get_type(&self) -> ElementType { + ElementType::Rect + } + + fn atts(&self) -> &[Att] { + &self.atts + } + + fn to_string(&self) -> String { + format!( + r#""#, + self.atts.iter().map(att_str3).collect::() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rect() { + let rect = rect().x(0).y(0).width(10).height(10); + assert_eq!( + r#""#, + rect.to_string() + ) + } +} diff --git a/src/render/svglib/svg.rs b/src/render/svglib/svg.rs new file mode 100644 index 0000000..39ad8a7 --- /dev/null +++ b/src/render/svglib/svg.rs @@ -0,0 +1,111 @@ +use crate::render::svglib::{att_str2, Att, Element, Value}; + +pub fn svg() -> Svg { + Svg::new() +} + +pub struct Svg { + style: Option, + elements: Vec>, + width: Option, + height: Option, + viewbox: Option, + preserveaspectratio: Option, + transform: Option, + atts: Vec, +} + +impl Svg { + pub fn new() -> Self { + Self { + style: None, + elements: Vec::new(), + width: None, + height: None, + viewbox: None, + preserveaspectratio: None, + transform: None, + atts: vec![], + } + } + + pub fn style(&mut self, style: &str) { + self.style = Some(style.to_string()); + } + + pub fn add(&mut self, child: impl Element + 'static) { + self.elements.push(Box::new(child)); + } + + pub fn width>(&mut self, width: V) { + self.width = Some(width.into().to_string()); + } + + pub fn height>(&mut self, height: V) { + self.height = Some(height.into().to_string()); + } + + pub fn viewbox(&mut self, viewbox: &str) { + self.viewbox = Some(viewbox.to_string()); + } + + pub fn preserveaspectratio(&mut self, preserveaspectratio: &str) { + self.preserveaspectratio = Some(preserveaspectratio.to_string()); + } + + fn transform>(&mut self, value: V) { + self.transform = Some(value.into().to_string()); + } + + pub fn to_string(&self) -> String { + let mut svg = String::new(); + svg.push_str( + format!( + r#"{}"#, + att_str2("width", &self.width), + att_str2("height", &self.height), + att_str2("viewBox", &self.viewbox), + att_str2("preserveAspectRatio", &self.preserveaspectratio), + att_str2("transform", &self.transform), + self.style + .as_ref() + .map(|s| format!("", s.to_string())) + .unwrap_or("".to_string()) + ) + .as_str(), + ); + + for e in &self.elements { + svg.push_str(e.to_string().as_str()); + } + svg.push_str(""); + svg + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::render::svglib::rect::rect; + + #[test] + fn style() { + let mut svg = Svg::new(); + svg.style(".id { background-color: red; }"); + assert_eq!( + r#""#, + svg.to_string() + ) + } + + #[test] + fn add_rect() { + let mut svg = Svg::new(); + svg.preserveaspectratio("none"); + svg.add(rect().x(0).y(0).width(10).height(10)); + assert_eq!( + r#""#, + svg.to_string() + ) + } +} diff --git a/src/render/svglib/text.rs b/src/render/svglib/text.rs new file mode 100644 index 0000000..6a0efa6 --- /dev/null +++ b/src/render/svglib/text.rs @@ -0,0 +1,105 @@ +use crate::render::svglib::{att, att_str3, Att, Element, ElementType, Value}; + +pub fn text() -> Text { + Text::new() +} + +pub struct Text { + atts: Vec, + text: String, +} + +impl Text { + pub fn new() -> Self { + let mut atts = vec![]; + Self { + atts, + text: "".to_string(), + } + } + + pub fn text>(mut self, text: V) -> Self { + self.text = text.into(); + self + } + + pub fn rounded() -> Self { + Self { + atts: vec![], + text: "".to_string(), + } + } + + pub fn x>(mut self, x: V) -> Self { + self.atts.push(att("x", x)); + self + } + pub fn y>(mut self, y: V) -> Self { + self.atts.push(att("y", y)); + self + } + pub fn width>(mut self, width: V) -> Self { + self.atts.push(att("width", width)); + self + } + pub fn height>(mut self, height: V) -> Self { + self.atts.push(att("height", height)); + self + } + pub fn class>(mut self, class: V) -> Self { + self.atts.push(att("class", class)); + self + } + + pub fn fill>(mut self, value: V) -> Self { + self.atts.push(att("fill", value)); + self + } + + pub fn stroke>(mut self, value: V) -> Self { + self.atts.push(att("stroke", value)); + self + } + + fn transform>(mut self, value: V) -> Self { + self.atts.push(att("transform", value)); + self + } + + pub fn attr>(mut self, key: &str, value: V) -> Self { + self.atts.push(att(key, value)); + self + } +} + +impl Element for Text { + fn get_type(&self) -> ElementType { + ElementType::Rect + } + + fn atts(&self) -> &[Att] { + &self.atts + } + + fn to_string(&self) -> String { + format!( + r#"{}"#, + self.atts.iter().map(att_str3).collect::(), + self.text, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rect() { + let rect = text().x(0).y(0).width(10).height(10); + assert_eq!( + r#""#, + rect.to_string() + ) + } +} diff --git a/src/render/svgrender2.rs b/src/render/svgrender2.rs new file mode 100644 index 0000000..a2e460f --- /dev/null +++ b/src/render/svgrender2.rs @@ -0,0 +1,10 @@ +use crate::render::Renderer; +use crate::Vis; + +struct SvgRender; + +impl Renderer for SvgRender { + fn render(&self, vis: Vis) -> anyhow::Result> { + Ok(vec![]) + } +}