back references for visnodes

This commit is contained in:
Shautvast 2025-01-25 13:35:08 +01:00
parent f02d8e419e
commit ad35f289c7
19 changed files with 354 additions and 220 deletions

15
bank.svg Normal file
View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 1387 135"><style>svg {
background-color: gray;
}
.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;
}
</style><rect id="calc" x="45" y="45" width="61" height="15" stroke="white" fill="none" /><text x="75" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="75" dy="10">Calculation</tspan> /><rect id="acc_interest_calc" x="121" y="45" width="145" height="15" stroke="white" fill="none" /><text x="193" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="193" dy="10">Account interest Calculation</tspan> /><rect id="interest_rates" x="281" y="45" width="76" height="15" stroke="white" fill="none" /><text x="319" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="319" dy="10">Interest Rates</tspan> /><rect id="config" x="372" y="45" width="71" height="15" stroke="white" fill="none" /><text x="407" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="407" dy="10">Configuration</tspan> /><rect id="nob_execution" x="473" y="60" width="71" height="15" stroke="white" fill="none" /><text x="508" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="508" dy="10">NoB Execution</tspan> /><rect id="coll_reinst_inst" x="559" y="60" width="204" height="15" stroke="white" fill="none" /><text x="661" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="661" dy="10">Collection of Reinstatement instructions</tspan> /><rect id="nob" x="458" y="45" width="320" height="45" stroke="white" fill="none" /><text x="618" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="618" dy="10">NoB</tspan> /><rect id="reporting" x="793" y="45" width="52" height="15" stroke="white" fill="none" /><text x="819" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="819" dy="10">Reporting</tspan> /><rect id="functions" x="30" y="30" width="830" height="75" stroke="white" fill="none" /><text x="445" y="30" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="bank_motor" x="905" y="60" width="57" height="15" stroke="white" fill="none" /><text x="933" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="933" dy="10">Bank Motor</tspan> /><rect id="bank_scripts" x="977" y="60" width="66" height="15" stroke="white" fill="none" /><text x="1010" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1010" dy="10">Bank Scripts</tspan> /><rect id="bank_client" x="1058" y="60" width="61" height="15" stroke="white" fill="none" /><text x="1088" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1088" dy="10">Bank Client</tspan> /><rect id="bank_db" x="1134" y="60" width="42" height="15" stroke="white" fill="none" /><text x="1155" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1155" dy="10">Bank DB</tspan> /><rect id="bank" x="890" y="45" width="301" height="45" stroke="white" fill="none" /><text x="1040" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1040" dy="10">Bank</tspan> /><rect id="interest_engine" x="1206" y="45" width="76" height="15" stroke="white" fill="none" /><text x="1244" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1244" dy="10">InterestEngine</tspan> /><rect id="systems" x="875" y="30" width="422" height="75" stroke="white" fill="none" /><text x="1086" y="30" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="lanes" x="15" y="15" width="1297" height="105" stroke="white" fill="none" /><text x="663" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="bank" x="1327" y="15" width="0" height="0" stroke="white" fill="none" /><text x="1327" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="bank_scripts" x="1342" y="15" width="0" height="0" stroke="white" fill="none" /><text x="1342" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="bank_motor" x="1357" y="15" width="0" height="0" stroke="white" fill="none" /><text x="1357" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="interest_engine" x="1372" y="15" width="0" height="0" stroke="white" fill="none" /><text x="1372" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -28,6 +28,9 @@ structure {
}
styles {
structure(group){
orientation: vertical
}
lanes(group) {
type: textnode
orientation: horizontal

View file

@ -1,4 +1,4 @@
<svg width="100" height="100" viewBox="0 0 15 0" xmlns="http://www.w3.org/2000/svg"><style>svg {
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 230 75"><style>svg {
background-color: gray;
}
@ -12,4 +12,4 @@
font-size: 10px;
font-family: sans-serif;
}
</style></svg>
</style><rect id="node1" x="15" y="15" width="37" height="30" stroke="white" fill="none" /><text x="33" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="33" dy="10">Node 1</tspan><tspan x="33" dy="10">longer</tspan> /><rect id="node2" x="67" y="15" width="37" height="15" stroke="white" fill="none" /><text x="85" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="85" dy="10">Node 2</tspan> /><rect id="node3" x="119" y="15" width="96" height="45" stroke="white" fill="none" /><text x="167" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="167" dy="10">Node 3</tspan><tspan x="167" dy="10">is longer</tspan><tspan x="167" dy="10">than you are to me</tspan> /></svg>

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -1,61 +1,125 @@
pub mod parse;
pub mod render;
use crate::Element::{Edge, Node};
use parse::tokens::TokenType;
use crate::parse::tokens::TokenType;
use std::collections::HashMap;
use std::fmt::Display;
#[derive(Debug)]
pub struct Vis {
pub structure: Vec<Element>,
pub structure: VisNode,
pub styles: Vec<StyleNode>,
}
impl Vis {
pub fn get_node(&self, id: &str) -> Option<&VisNode> {
if self.structure.id == id {
return Some(&self.structure);
} else {
for child in &self.structure.children {
if child.id == id {
return Some(child);
}
}
}
None
}
pub fn get_style(&self, node: &VisNode) -> Option<&StyleNode> {
let style = self.styles.iter().find(|s| s.id_ref == node.id);
if style.is_none() && node.id != "structure" {
for child in &node.children {
if let Some(style) = self.get_style(child) {
return Some(style);
}
}
None
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NodeType {
Node,
Edge,
}
#[derive(Debug, Clone)]
pub enum Element {
Node(String, Option<String>, Vec<Element>),
Edge(String, String, TokenType, Option<String>),
pub struct VisNode {
pub node_type: NodeType,
pub id: String,
targetid: Option<String>,
pub label: Option<String>,
pub children: Vec<VisNode>,
pub parent: Option<String>,
edgetype: Option<TokenType>,
}
impl Element {
pub fn new_node(id: &str, label: Option<&str>, children: Vec<Element>) -> Element {
Element::Node(id.into(), label.map(|s| s.into()), children)
impl VisNode {
pub fn new_node(
id: impl Into<String>,
label: Option<impl Into<String>>,
children: Vec<VisNode>,
) -> Self {
Self {
id: id.into(),
targetid: None,
label: label.map(|s| s.into()),
children,
parent: None,
node_type: NodeType::Node,
edgetype: None,
}
}
pub fn new_edge(
id: String,
source: String,
token: TokenType,
label: Option<String>,
) -> Element {
Edge(id, source, token, label)
sourceid: impl Into<String>,
targetid: impl Into<String>,
edgetype: TokenType,
label: Option<impl Into<String>>,
) -> Self {
Self {
id: sourceid.into(),
targetid: Some(targetid.into()),
edgetype: Some(edgetype),
label: label.map(|l| l.into()),
children: vec![],
parent: None,
node_type: NodeType::Edge,
}
}
}
impl Display for Element {
impl Display for VisNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Node(id, label, children) => {
match &self.node_type {
NodeType::Node => {
let mut string = String::new();
string.push_str(&format!(
"Node {{{}: {}",
id,
label.as_ref().unwrap_or(&"".to_string())
self.id,
self.label.as_ref().unwrap_or(&"".to_string())
));
for child in children {
for child in &self.children {
string.push_str(&format!(" {}", child));
}
string.push('}');
write!(f, "{}", string)
}
Edge(id, source, token, label) => {
let mut string = "Edge {{".to_string();
string.push_str(&format!("{} {} {:?}", id, source, token));
if let Some(label) = label {
string.push_str(&format!(" {}", label));
NodeType::Edge => {
write!(f, "Edge {{")?;
write!(
f,
"{} {} {:?}",
self.id,
self.targetid.as_ref().unwrap(),
self.edgetype
)?;
if let Some(label) = &self.label {
write!(f, " {}", label)?;
}
string.push('}');
write!(f, "{}", string)
write!(f, "}}")
}
}
}

View file

@ -3,7 +3,7 @@ use crate::{
Token,
TokenType::{self, *},
},
ContainerType, Element, StyleNode, Vis,
ContainerType, StyleNode, Vis, VisNode,
};
use anyhow::anyhow;
use std::collections::HashMap;
@ -13,10 +13,21 @@ pub fn parse_vis(contents: &str) -> anyhow::Result<Vis> {
// println!("{:?}", tokens);
let mut parser = Parser::new(tokens);
Ok(Vis {
structure: parser.structure()?,
let structure = parser.structure()?;
let mut vis = Vis {
structure: VisNode::new_node("structure", None::<String>, structure),
styles: parser.styles()?,
})
};
// add bottom up references (sorry no actual refs, but clones)
vis.structure.children.iter_mut().for_each(|node| {
let c = node.clone();
node.children.iter_mut().for_each(|child| {
child.parent.replace(c.id.to_owned());
});
});
Ok(vis)
}
struct Parser {
@ -29,7 +40,7 @@ impl Parser {
Self { tokens, current: 0 }
}
fn structure(&mut self) -> anyhow::Result<Vec<Element>> {
fn structure(&mut self) -> anyhow::Result<Vec<VisNode>> {
if self.match_token(Structure) {
self.elements()
} else {
@ -37,7 +48,7 @@ impl Parser {
}
}
fn elements(&mut self) -> anyhow::Result<Vec<Element>> {
fn elements(&mut self) -> anyhow::Result<Vec<VisNode>> {
// println!("nodes {:?}", self.peek());
self.consume(LeftBrace, "Expected '{'")?;
let mut nodes = vec![];
@ -47,7 +58,7 @@ impl Parser {
Ok(nodes)
}
fn element(&mut self) -> anyhow::Result<Element> {
fn element(&mut self) -> anyhow::Result<VisNode> {
// println!("node {:?}", self.peek());
let id = self.id()?;
// println!("id {}", id);
@ -68,14 +79,14 @@ impl Parser {
vec![]
};
Ok(Element::Node(id, title, children))
Ok(VisNode::new_node(id, title, children))
}
}
fn edge(&mut self, from_id: String, arrow: Token) -> anyhow::Result<Element> {
fn edge(&mut self, from_id: String, arrow: Token) -> anyhow::Result<VisNode> {
let to_id = self.id()?;
let title = self.title()?;
Ok(Element::Edge(from_id, to_id, arrow.tokentype, title))
Ok(VisNode::new_edge(from_id, to_id, arrow.tokentype, title))
}
fn title(&mut self) -> anyhow::Result<Option<String>> {
@ -119,8 +130,13 @@ impl Parser {
}
fn style(&mut self) -> anyhow::Result<StyleNode> {
if self.check(&Identifier) {
let idref = self.peek().lexeme.to_owned();
if self.check(&Identifier) || self.check(&Structure) {
let idref = if self.check(&Structure) {
// only structure element can also be referenced
"structure".to_string()
} else {
self.peek().lexeme.to_owned()
};
self.advance();
let containertype = self.containertype()?;
self.consume(RightParen, "Expected ')'")?;
@ -227,7 +243,7 @@ impl Parser {
#[cfg(test)]
mod tests {
use crate::Element;
use crate::NodeType::Node;
#[test]
fn test_parse() {
@ -244,28 +260,32 @@ mod tests {
if let Some(vis) = vis {
// Validate the parsed structure by asserting its elements
assert_eq!(
vis.structure.len(),
vis.structure.children.len(),
1,
"The structure should contain one top-level element"
);
if let Element::Node(id, title, children) = &vis.structure[0] {
assert_eq!(id, "top", "The ID of the first node should be 'top'");
if vis.structure.children[0].node_type == Node {
let node = &vis.structure.children[0];
assert_eq!(node.id, "top", "The ID of the first node should be 'top'");
assert_eq!(
title.as_ref().unwrap(),
node.label.as_ref().unwrap(),
&"top-node".to_owned(),
"The title of the first node should be 'top-node'"
);
assert_eq!(children.len(), 1);
let child = &children[0];
if let Element::Node(id, title, children) = child {
assert_eq!(id, "child", "The ID of the second node should be 'child'");
assert_eq!(node.children.len(), 1);
let child = &node.children[0];
if child.node_type == Node {
assert_eq!(
title.as_ref().unwrap(),
child.id, "child",
"The ID of the second node should be 'child'"
);
assert_eq!(
child.label.as_ref().unwrap(),
&"child-node".to_owned(),
"The title of the second node should be 'child-node'"
);
assert_eq!(children.len(), 0);
assert_eq!(child.children.len(), 0);
}
} else {
panic!("The top-level element should be a Node");

View file

@ -2,8 +2,7 @@ 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};
use crate::{StyleNode, Vis, VisNode};
pub struct SvgRender;
@ -15,7 +14,7 @@ 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, &vis.styles, 0, 0);
let (w, h) = render_elements(&mut svg, &vis.structure.children, &vis.styles, 0, 0);
svg.viewbox(format!("0 0 {} {}", w, h));
Ok(svg.to_string().into_bytes())
}
@ -23,7 +22,7 @@ impl Renderer for SvgRender {
fn render_elements(
svg: &mut Svg,
elements: &Vec<Element>,
elements: &[VisNode],
_styles: &Vec<StyleNode>,
start_x: u32,
start_y: u32,
@ -36,10 +35,9 @@ fn render_elements(
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 (width, height) = if !element.children.is_empty() {
render_elements(svg, &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;
((label_width as f32 * 4.9 + 8.0) as u32, label_height * 15)
@ -52,7 +50,7 @@ fn render_elements(
svg.add(
rect()
.id(id)
.id(element.id.clone())
.x(x + padding)
.y(y + padding)
.width(width)
@ -66,14 +64,13 @@ fn render_elements(
.x(x + padding + width / 2)
.y(start_y + padding)
.fill("white")
.text(label.as_ref().map(|l| l.as_str()).unwrap_or(""))
.text(element.label.as_ref().unwrap_or(&"".to_string()))
.attr("text-anchor", "middle")
.attr("stroke-width", "1")
.class("node"),
);
x += width + padding;
}
}
(total_width + padding, total_height + padding)
}
@ -81,7 +78,7 @@ fn render_elements(
#[cfg(test)]
mod tests {
use super::*;
use crate::{ContainerType, StyleNode, Vis};
use crate::{ContainerType, StyleNode, Vis, VisNode};
use std::fs::File;
use std::io::Write;
@ -98,32 +95,32 @@ mod tests {
};
// Create mock Elements
let element1 = Node(
"node1".to_string(),
Some("Node 1\nlonger".to_string()),
let element1 = VisNode::new_node(
"node1",
Some("Node 1\nlonger"),
vec![], // No child elements
);
let element2 = Node(
"node2".to_string(),
Some("Node 2".to_string()),
let element2 = VisNode::new_node(
"node2",
Some("Node 2"),
vec![], // No child elements
);
let element3 = Node(
"node3".to_string(),
Some("Node 3\nis longer\nthan you are to me".to_string()),
let element3 = VisNode::new_node(
"node3",
Some("Node 3\nis longer\nthan you are to me"),
vec![], // No child elements
);
let root = Node(
"root".to_string(),
Some("root".to_string()),
let root = VisNode::new_node(
"structure",
Some("root"),
vec![element1, element2, element3],
);
// Create Vis structure
let vis = Vis {
styles: vec![style_node],
structure: vec![root],
structure: root,
};
let svg_output = SvgRender {}.render(vis).unwrap();

View file

@ -1,4 +1,5 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Shape, Value};
use crate::render::svglib::{att, att_to_string, Att, SvgElement, ElementType, Shape, Value};
use std::fmt::Display;
pub fn circle() -> Circle {
Circle::default()
@ -43,17 +44,20 @@ impl Shape for Circle {
}
}
impl Element for Circle {
fn get_type(&self) -> ElementType {
ElementType::Circle
}
fn to_string(&self) -> String {
format!(
impl Display for Circle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r#"<circle{} />"#,
self.0.iter().map(att_to_string).collect::<String>()
)
}
}
impl SvgElement for Circle {
fn get_type(&self) -> ElementType {
ElementType::Circle
}
fn atts(&self) -> &[Att] {
&self.0

View file

@ -1,4 +1,5 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Shape, Value};
use crate::render::svglib::{att, att_to_string, Att, ElementType, Shape, SvgElement, Value};
use std::fmt::Display;
pub fn ellipse() -> Ellipse {
Ellipse(vec![])
@ -46,7 +47,17 @@ impl Shape for Ellipse {
}
}
impl Element for Ellipse {
impl Display for Ellipse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r#"<ellipse{} />"#,
self.0.iter().map(att_to_string).collect::<String>()
)
}
}
impl SvgElement for Ellipse {
fn get_type(&self) -> ElementType {
ElementType::Ellipse
}
@ -54,13 +65,6 @@ impl Element for Ellipse {
fn atts(&self) -> &[Att] {
&self.0
}
fn to_string(&self) -> String {
format!(
r#"<ellipse{} />"#,
self.0.iter().map(att_to_string).collect::<String>()
)
}
}
#[cfg(test)]

View file

@ -1,5 +1,6 @@
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, ElementType, SvgElement, Value};
use std::fmt::Display;
pub fn foreign_object() -> ForeignObject {
ForeignObject::default()
@ -47,7 +48,21 @@ impl ForeignObject {
}
}
impl Element for ForeignObject {
impl Display for ForeignObject {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r#"<foreignObject{}>{}</foreignObject>"#,
self.atts.iter().map(att_to_string).collect::<String>(),
self.child
.as_ref()
.map(|c| c.to_string())
.unwrap_or("".to_string())
)
}
}
impl SvgElement for ForeignObject {
fn get_type(&self) -> ElementType {
ElementType::ForeignObject
}
@ -55,15 +70,4 @@ impl Element for ForeignObject {
fn atts(&self) -> &[Att] {
&self.atts
}
fn to_string(&self) -> String {
format!(
r#"<foreignObject{}>{}</foreignObject>"#,
self.atts.iter().map(att_to_string).collect::<String>(),
self.child
.as_ref()
.map(|c| c.to_string())
.unwrap_or("".to_string()),
)
}
}

View file

@ -1,19 +1,18 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
use std::fmt::Display;
pub fn group() -> Group {
Group {
children: Vec::new(),
atts: vec![],
}
Group::default()
}
#[derive(Default)]
pub struct Group {
children: Vec<Box<dyn Element>>,
children: Vec<Box<dyn SvgElement>>,
atts: Vec<Att>,
}
impl Group {
pub fn add(&mut self, child: impl Element + 'static) {
pub fn add(&mut self, child: impl SvgElement + 'static) {
self.children.push(Box::new(child));
}
@ -26,7 +25,21 @@ impl Group {
}
}
impl Element for Group {
impl Display for Group {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"<g{}>",
self.atts.iter().map(att_to_string).collect::<String>()
)?;
for e in &self.children {
write!(f, "{}", e.to_string().as_str())?;
}
write!(f, "</g>")
}
}
impl SvgElement for Group {
fn get_type(&self) -> ElementType {
ElementType::Group(Vec::new())
}
@ -34,19 +47,6 @@ impl Element for Group {
fn atts(&self) -> &[Att] {
&self.atts
}
fn to_string(&self) -> String {
let mut svg = format!(
"<g{}>",
self.atts.iter().map(att_to_string).collect::<String>()
);
for e in &self.children {
svg.push_str(e.to_string().as_str());
}
svg.push_str("</g>");
svg
}
}
#[cfg(test)]

View file

@ -1,4 +1,5 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
use std::fmt::Display;
pub fn image() -> Image {
Image::default()
@ -39,17 +40,20 @@ impl Image {
}
}
impl Element for Image {
fn get_type(&self) -> ElementType {
ElementType::Image
}
fn to_string(&self) -> String {
format!(
impl Display for Image {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r#"<image{} />"#,
self.atts.iter().map(att_to_string).collect::<String>()
)
}
}
impl SvgElement for Image {
fn get_type(&self) -> ElementType {
ElementType::Image
}
fn atts(&self) -> &[Att] {
&self.atts

View file

@ -1,4 +1,5 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Shape, Value};
use crate::render::svglib::{att, att_to_string, Att, SvgElement, ElementType, Shape, Value};
use std::fmt::Display;
pub fn line() -> Line {
Line(vec![])
@ -34,7 +35,17 @@ impl Line {
}
}
impl Element for Line {
impl Display for Line {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r#" <line{} />"#,
self.0.iter().map(att_to_string).collect::<String>()
)
}
}
impl SvgElement for Line {
fn get_type(&self) -> ElementType {
ElementType::Line
}
@ -42,13 +53,6 @@ impl Element for Line {
fn atts(&self) -> &[Att] {
&self.0
}
fn to_string(&self) -> String {
format!(
r#" <line{} />"#,
self.0.iter().map(att_to_string).collect::<String>()
)
}
}
impl Shape for Line {

View file

@ -1,4 +1,5 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
use crate::render::svglib::{att, att_to_string, Att, SvgElement, ElementType, Value};
use std::fmt::Display;
pub fn link(href: &str) -> Link {
Link(vec![att("href", href)])
@ -12,17 +13,20 @@ impl Link {
}
}
impl Element for Link {
fn get_type(&self) -> ElementType {
ElementType::Link
}
fn to_string(&self) -> String {
format!(
impl Display for Link {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r#"<link{} />"#,
self.0.iter().map(att_to_string).collect::<String>()
)
}
}
impl SvgElement for Link {
fn get_type(&self) -> ElementType {
ElementType::Link
}
fn atts(&self) -> &[Att] {
&self.0

View file

@ -76,9 +76,8 @@ pub enum ElementType {
Rect,
}
pub trait Element {
pub trait SvgElement: Display {
fn get_type(&self) -> ElementType;
fn to_string(&self) -> String;
fn atts(&self) -> &[Att];
}

View file

@ -1,4 +1,5 @@
use crate::render::svglib::{att, Att, Element, ElementType, Shape, Value};
use crate::render::svglib::{att, att_to_string, Att, ElementType, Shape, SvgElement, Value};
use std::fmt;
pub fn path(d: &str) -> Path {
Path::new(d)
@ -9,15 +10,22 @@ pub struct Path {
atts: Vec<Att>,
}
impl Element for Path {
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
r#"<path d="{}"{} />"#,
self.d,
self.atts.iter().map(att_to_string).collect::<String>()
)
}
}
impl SvgElement for Path {
fn get_type(&self) -> ElementType {
ElementType::Path
}
fn to_string(&self) -> String {
todo!()
}
fn atts(&self) -> &[Att] {
&self.atts
}

View file

@ -1,4 +1,4 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
pub fn rect() -> Rect {
Rect::default()
@ -60,7 +60,17 @@ impl Rect {
}
}
impl Element for Rect {
impl std::fmt::Display for Rect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r#"<rect{} />"#,
self.atts.iter().map(att_to_string).collect::<String>()
)
}
}
impl SvgElement for Rect {
fn get_type(&self) -> ElementType {
ElementType::Rect
}
@ -68,13 +78,6 @@ impl Element for Rect {
fn atts(&self) -> &[Att] {
&self.atts
}
fn to_string(&self) -> String {
format!(
r#"<rect{} />"#,
self.atts.iter().map(att_to_string).collect::<String>()
)
}
}
#[cfg(test)]

View file

@ -1,4 +1,4 @@
use crate::render::svglib::{att, att_to_string, Att, Element, Value};
use crate::render::svglib::{att, att_to_string, Att, SvgElement, Value};
use std::fmt;
pub fn svg() -> Svg {
@ -8,7 +8,7 @@ pub fn svg() -> Svg {
#[derive(Default)]
pub struct Svg {
style: Option<String>,
elements: Vec<Box<dyn Element>>,
elements: Vec<Box<dyn SvgElement>>,
atts: Vec<Att>,
}
@ -17,7 +17,7 @@ impl Svg {
self.style = Some(style.to_string());
}
pub fn add(&mut self, child: impl Element + 'static) {
pub fn add(&mut self, child: impl SvgElement + 'static) {
self.elements.push(Box::new(child));
}
@ -35,7 +35,7 @@ impl Svg {
pub fn preserveaspectratio<V: Into<Value>>(&mut self, preserveaspectratio: V) {
self.atts
.push(att("preserveaspectratio", preserveaspectratio));
.push(att("preserveAspectRatio", preserveaspectratio));
}
pub fn transform<V: Into<Value>>(&mut self, transform: V) {
@ -47,7 +47,7 @@ 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" {}>{}"#,
r#"<svg xmlns="http://www.w3.org/2000/svg"{}>{}"#,
self.atts.iter().map(att_to_string).collect::<String>(),
self.style
.as_ref()
@ -83,7 +83,7 @@ mod tests {
svg.preserveaspectratio("none");
svg.add(rect().x(0).y(0).width(10).height(10));
assert_eq!(
r#"<svg preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg"><rect x="0" y="0" width="10" height="10" /></svg>"#,
r#"<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none"><rect x="0" y="0" width="10" height="10" /></svg>"#,
svg.to_string()
)
}

View file

@ -1,5 +1,5 @@
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
use std::fmt::Write;
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
use std::fmt::{Display, Write};
pub fn text() -> Text {
Text::default()
@ -74,16 +74,11 @@ impl Text {
}
}
impl Element for Text {
fn get_type(&self) -> ElementType {
ElementType::Rect
}
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();
format!(
r#"<text{}>{}</text>"#,
impl Display for Text {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r#"<text{}{} />"#,
self.atts.iter().map(att_to_string).collect::<String>(),
self.text.lines().fold(String::new(), |mut output, l| {
let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect();
@ -93,6 +88,12 @@ impl Element for Text {
})
)
}
}
impl SvgElement for Text {
fn get_type(&self) -> ElementType {
ElementType::Rect
}
fn atts(&self) -> &[Att] {
&self.atts
@ -105,10 +106,10 @@ mod tests {
#[test]
fn test_rect() {
let rect = text().x(0).y(0).width(10).height(10);
let text = text().x(0).y(0).width(10).height(10);
assert_eq!(
r#"<rect x="0" y="0" width="10" height="10" />"#,
rect.to_string()
r#"<text x="0" y="0" width="10" height="10" />"#,
text.to_string()
)
}
}