back references for visnodes
This commit is contained in:
parent
f02d8e419e
commit
ad35f289c7
19 changed files with 354 additions and 220 deletions
15
bank.svg
Normal file
15
bank.svg
Normal 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 |
|
|
@ -28,6 +28,9 @@ structure {
|
||||||
}
|
}
|
||||||
|
|
||||||
styles {
|
styles {
|
||||||
|
structure(group){
|
||||||
|
orientation: vertical
|
||||||
|
}
|
||||||
lanes(group) {
|
lanes(group) {
|
||||||
type: textnode
|
type: textnode
|
||||||
orientation: horizontal
|
orientation: horizontal
|
||||||
|
|
|
||||||
|
|
@ -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;
|
background-color: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -12,4 +12,4 @@
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-family: sans-serif;
|
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
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
122
src/lib.rs
122
src/lib.rs
|
|
@ -1,61 +1,125 @@
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
|
||||||
use crate::Element::{Edge, Node};
|
use crate::parse::tokens::TokenType;
|
||||||
use parse::tokens::TokenType;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Vis {
|
pub struct Vis {
|
||||||
pub structure: Vec<Element>,
|
pub structure: VisNode,
|
||||||
pub styles: Vec<StyleNode>,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Element {
|
pub struct VisNode {
|
||||||
Node(String, Option<String>, Vec<Element>),
|
pub node_type: NodeType,
|
||||||
Edge(String, String, TokenType, Option<String>),
|
pub id: String,
|
||||||
|
targetid: Option<String>,
|
||||||
|
pub label: Option<String>,
|
||||||
|
pub children: Vec<VisNode>,
|
||||||
|
pub parent: Option<String>,
|
||||||
|
edgetype: Option<TokenType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element {
|
impl VisNode {
|
||||||
pub fn new_node(id: &str, label: Option<&str>, children: Vec<Element>) -> Element {
|
pub fn new_node(
|
||||||
Element::Node(id.into(), label.map(|s| s.into()), children)
|
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(
|
pub fn new_edge(
|
||||||
id: String,
|
sourceid: impl Into<String>,
|
||||||
source: String,
|
targetid: impl Into<String>,
|
||||||
token: TokenType,
|
edgetype: TokenType,
|
||||||
label: Option<String>,
|
label: Option<impl Into<String>>,
|
||||||
) -> Element {
|
) -> Self {
|
||||||
Edge(id, source, token, label)
|
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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match &self.node_type {
|
||||||
Node(id, label, children) => {
|
NodeType::Node => {
|
||||||
let mut string = String::new();
|
let mut string = String::new();
|
||||||
string.push_str(&format!(
|
string.push_str(&format!(
|
||||||
"Node {{{}: {}",
|
"Node {{{}: {}",
|
||||||
id,
|
self.id,
|
||||||
label.as_ref().unwrap_or(&"".to_string())
|
self.label.as_ref().unwrap_or(&"".to_string())
|
||||||
));
|
));
|
||||||
for child in children {
|
for child in &self.children {
|
||||||
string.push_str(&format!(" {}", child));
|
string.push_str(&format!(" {}", child));
|
||||||
}
|
}
|
||||||
string.push('}');
|
string.push('}');
|
||||||
write!(f, "{}", string)
|
write!(f, "{}", string)
|
||||||
}
|
}
|
||||||
Edge(id, source, token, label) => {
|
NodeType::Edge => {
|
||||||
let mut string = "Edge {{".to_string();
|
write!(f, "Edge {{")?;
|
||||||
string.push_str(&format!("{} {} {:?}", id, source, token));
|
write!(
|
||||||
if let Some(label) = label {
|
f,
|
||||||
string.push_str(&format!(" {}", label));
|
"{} {} {:?}",
|
||||||
|
self.id,
|
||||||
|
self.targetid.as_ref().unwrap(),
|
||||||
|
self.edgetype
|
||||||
|
)?;
|
||||||
|
if let Some(label) = &self.label {
|
||||||
|
write!(f, " {}", label)?;
|
||||||
}
|
}
|
||||||
string.push('}');
|
write!(f, "}}")
|
||||||
write!(f, "{}", string)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
Token,
|
Token,
|
||||||
TokenType::{self, *},
|
TokenType::{self, *},
|
||||||
},
|
},
|
||||||
ContainerType, Element, StyleNode, Vis,
|
ContainerType, StyleNode, Vis, VisNode,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -13,10 +13,21 @@ pub fn parse_vis(contents: &str) -> anyhow::Result<Vis> {
|
||||||
// println!("{:?}", tokens);
|
// println!("{:?}", tokens);
|
||||||
let mut parser = Parser::new(tokens);
|
let mut parser = Parser::new(tokens);
|
||||||
|
|
||||||
Ok(Vis {
|
let structure = parser.structure()?;
|
||||||
structure: parser.structure()?,
|
|
||||||
|
let mut vis = Vis {
|
||||||
|
structure: VisNode::new_node("structure", None::<String>, structure),
|
||||||
styles: parser.styles()?,
|
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 {
|
struct Parser {
|
||||||
|
|
@ -29,7 +40,7 @@ impl Parser {
|
||||||
Self { tokens, current: 0 }
|
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) {
|
if self.match_token(Structure) {
|
||||||
self.elements()
|
self.elements()
|
||||||
} else {
|
} 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());
|
// println!("nodes {:?}", self.peek());
|
||||||
self.consume(LeftBrace, "Expected '{'")?;
|
self.consume(LeftBrace, "Expected '{'")?;
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
|
|
@ -47,7 +58,7 @@ impl Parser {
|
||||||
Ok(nodes)
|
Ok(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element(&mut self) -> anyhow::Result<Element> {
|
fn element(&mut self) -> anyhow::Result<VisNode> {
|
||||||
// println!("node {:?}", self.peek());
|
// println!("node {:?}", self.peek());
|
||||||
let id = self.id()?;
|
let id = self.id()?;
|
||||||
// println!("id {}", id);
|
// println!("id {}", id);
|
||||||
|
|
@ -68,14 +79,14 @@ impl Parser {
|
||||||
vec![]
|
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 to_id = self.id()?;
|
||||||
let title = self.title()?;
|
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>> {
|
fn title(&mut self) -> anyhow::Result<Option<String>> {
|
||||||
|
|
@ -119,8 +130,13 @@ impl Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&mut self) -> anyhow::Result<StyleNode> {
|
fn style(&mut self) -> anyhow::Result<StyleNode> {
|
||||||
if self.check(&Identifier) {
|
if self.check(&Identifier) || self.check(&Structure) {
|
||||||
let idref = self.peek().lexeme.to_owned();
|
let idref = if self.check(&Structure) {
|
||||||
|
// only structure element can also be referenced
|
||||||
|
"structure".to_string()
|
||||||
|
} else {
|
||||||
|
self.peek().lexeme.to_owned()
|
||||||
|
};
|
||||||
self.advance();
|
self.advance();
|
||||||
let containertype = self.containertype()?;
|
let containertype = self.containertype()?;
|
||||||
self.consume(RightParen, "Expected ')'")?;
|
self.consume(RightParen, "Expected ')'")?;
|
||||||
|
|
@ -227,7 +243,7 @@ impl Parser {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::Element;
|
use crate::NodeType::Node;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse() {
|
fn test_parse() {
|
||||||
|
|
@ -244,28 +260,32 @@ mod tests {
|
||||||
if let Some(vis) = vis {
|
if let Some(vis) = vis {
|
||||||
// Validate the parsed structure by asserting its elements
|
// Validate the parsed structure by asserting its elements
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vis.structure.len(),
|
vis.structure.children.len(),
|
||||||
1,
|
1,
|
||||||
"The structure should contain one top-level element"
|
"The structure should contain one top-level element"
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Element::Node(id, title, children) = &vis.structure[0] {
|
if vis.structure.children[0].node_type == Node {
|
||||||
assert_eq!(id, "top", "The ID of the first node should be 'top'");
|
let node = &vis.structure.children[0];
|
||||||
|
assert_eq!(node.id, "top", "The ID of the first node should be 'top'");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
title.as_ref().unwrap(),
|
node.label.as_ref().unwrap(),
|
||||||
&"top-node".to_owned(),
|
&"top-node".to_owned(),
|
||||||
"The title of the first node should be 'top-node'"
|
"The title of the first node should be 'top-node'"
|
||||||
);
|
);
|
||||||
assert_eq!(children.len(), 1);
|
assert_eq!(node.children.len(), 1);
|
||||||
let child = &children[0];
|
let child = &node.children[0];
|
||||||
if let Element::Node(id, title, children) = child {
|
if child.node_type == Node {
|
||||||
assert_eq!(id, "child", "The ID of the second node should be 'child'");
|
|
||||||
assert_eq!(
|
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(),
|
&"child-node".to_owned(),
|
||||||
"The title of the second node should be 'child-node'"
|
"The title of the second node should be 'child-node'"
|
||||||
);
|
);
|
||||||
assert_eq!(children.len(), 0);
|
assert_eq!(child.children.len(), 0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("The top-level element should be a Node");
|
panic!("The top-level element should be a Node");
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ use crate::render::svglib::rect::rect;
|
||||||
use crate::render::svglib::svg::{svg, Svg};
|
use crate::render::svglib::svg::{svg, Svg};
|
||||||
use crate::render::svglib::text::text;
|
use crate::render::svglib::text::text;
|
||||||
use crate::render::Renderer;
|
use crate::render::Renderer;
|
||||||
use crate::Element::Node;
|
use crate::{StyleNode, Vis, VisNode};
|
||||||
use crate::{Element, StyleNode, Vis};
|
|
||||||
|
|
||||||
pub struct SvgRender;
|
pub struct SvgRender;
|
||||||
|
|
||||||
|
|
@ -15,7 +14,7 @@ impl Renderer for SvgRender {
|
||||||
svg.height(100);
|
svg.height(100);
|
||||||
let style = include_str!("svg_node.css");
|
let style = include_str!("svg_node.css");
|
||||||
svg.style(style);
|
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));
|
svg.viewbox(format!("0 0 {} {}", w, h));
|
||||||
Ok(svg.to_string().into_bytes())
|
Ok(svg.to_string().into_bytes())
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +22,7 @@ impl Renderer for SvgRender {
|
||||||
|
|
||||||
fn render_elements(
|
fn render_elements(
|
||||||
svg: &mut Svg,
|
svg: &mut Svg,
|
||||||
elements: &Vec<Element>,
|
elements: &[VisNode],
|
||||||
_styles: &Vec<StyleNode>,
|
_styles: &Vec<StyleNode>,
|
||||||
start_x: u32,
|
start_x: u32,
|
||||||
start_y: u32,
|
start_y: u32,
|
||||||
|
|
@ -36,10 +35,9 @@ fn render_elements(
|
||||||
let y = start_y;
|
let y = start_y;
|
||||||
|
|
||||||
for element in elements {
|
for element in elements {
|
||||||
if let Node(id, label, children) = element {
|
let (width, height) = if !element.children.is_empty() {
|
||||||
let (width, height) = if !children.is_empty() {
|
render_elements(svg, &element.children, _styles, x + padding, y + padding)
|
||||||
render_elements(svg, children, _styles, x + padding, y + padding)
|
} else if let Some(label) = element.label.as_ref() {
|
||||||
} else if let Node(_, Some(label), _) = element {
|
|
||||||
let label_width = label.lines().map(|l| l.len()).max().unwrap_or(0) as u32;
|
let label_width = label.lines().map(|l| l.len()).max().unwrap_or(0) as u32;
|
||||||
let label_height = label.lines().count() as u32;
|
let label_height = label.lines().count() as u32;
|
||||||
((label_width as f32 * 4.9 + 8.0) as u32, label_height * 15)
|
((label_width as f32 * 4.9 + 8.0) as u32, label_height * 15)
|
||||||
|
|
@ -52,7 +50,7 @@ fn render_elements(
|
||||||
|
|
||||||
svg.add(
|
svg.add(
|
||||||
rect()
|
rect()
|
||||||
.id(id)
|
.id(element.id.clone())
|
||||||
.x(x + padding)
|
.x(x + padding)
|
||||||
.y(y + padding)
|
.y(y + padding)
|
||||||
.width(width)
|
.width(width)
|
||||||
|
|
@ -66,14 +64,13 @@ fn render_elements(
|
||||||
.x(x + padding + width / 2)
|
.x(x + padding + width / 2)
|
||||||
.y(start_y + padding)
|
.y(start_y + padding)
|
||||||
.fill("white")
|
.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("text-anchor", "middle")
|
||||||
.attr("stroke-width", "1")
|
.attr("stroke-width", "1")
|
||||||
.class("node"),
|
.class("node"),
|
||||||
);
|
);
|
||||||
x += width + padding;
|
x += width + padding;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
(total_width + padding, total_height + padding)
|
(total_width + padding, total_height + padding)
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +78,7 @@ fn render_elements(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ContainerType, StyleNode, Vis};
|
use crate::{ContainerType, StyleNode, Vis, VisNode};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
|
@ -98,32 +95,32 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create mock Elements
|
// Create mock Elements
|
||||||
let element1 = Node(
|
let element1 = VisNode::new_node(
|
||||||
"node1".to_string(),
|
"node1",
|
||||||
Some("Node 1\nlonger".to_string()),
|
Some("Node 1\nlonger"),
|
||||||
vec![], // No child elements
|
vec![], // No child elements
|
||||||
);
|
);
|
||||||
let element2 = Node(
|
let element2 = VisNode::new_node(
|
||||||
"node2".to_string(),
|
"node2",
|
||||||
Some("Node 2".to_string()),
|
Some("Node 2"),
|
||||||
vec![], // No child elements
|
vec![], // No child elements
|
||||||
);
|
);
|
||||||
|
|
||||||
let element3 = Node(
|
let element3 = VisNode::new_node(
|
||||||
"node3".to_string(),
|
"node3",
|
||||||
Some("Node 3\nis longer\nthan you are to me".to_string()),
|
Some("Node 3\nis longer\nthan you are to me"),
|
||||||
vec![], // No child elements
|
vec![], // No child elements
|
||||||
);
|
);
|
||||||
let root = Node(
|
let root = VisNode::new_node(
|
||||||
"root".to_string(),
|
"structure",
|
||||||
Some("root".to_string()),
|
Some("root"),
|
||||||
vec![element1, element2, element3],
|
vec![element1, element2, element3],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create Vis structure
|
// Create Vis structure
|
||||||
let vis = Vis {
|
let vis = Vis {
|
||||||
styles: vec![style_node],
|
styles: vec![style_node],
|
||||||
structure: vec![root],
|
structure: root,
|
||||||
};
|
};
|
||||||
|
|
||||||
let svg_output = SvgRender {}.render(vis).unwrap();
|
let svg_output = SvgRender {}.render(vis).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn circle() -> Circle {
|
||||||
Circle::default()
|
Circle::default()
|
||||||
|
|
@ -43,17 +44,20 @@ impl Shape for Circle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Circle {
|
impl Display for Circle {
|
||||||
fn get_type(&self) -> ElementType {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
ElementType::Circle
|
write!(
|
||||||
}
|
f,
|
||||||
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
format!(
|
|
||||||
r#"<circle{} />"#,
|
r#"<circle{} />"#,
|
||||||
self.0.iter().map(att_to_string).collect::<String>()
|
self.0.iter().map(att_to_string).collect::<String>()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SvgElement for Circle {
|
||||||
|
fn get_type(&self) -> ElementType {
|
||||||
|
ElementType::Circle
|
||||||
|
}
|
||||||
|
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.0
|
&self.0
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn ellipse() -> Ellipse {
|
||||||
Ellipse(vec![])
|
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 {
|
fn get_type(&self) -> ElementType {
|
||||||
ElementType::Ellipse
|
ElementType::Ellipse
|
||||||
}
|
}
|
||||||
|
|
@ -54,13 +65,6 @@ impl Element for Ellipse {
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
format!(
|
|
||||||
r#"<ellipse{} />"#,
|
|
||||||
self.0.iter().map(att_to_string).collect::<String>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::render::svglib::div::Div;
|
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 {
|
pub fn foreign_object() -> ForeignObject {
|
||||||
ForeignObject::default()
|
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 {
|
fn get_type(&self) -> ElementType {
|
||||||
ElementType::ForeignObject
|
ElementType::ForeignObject
|
||||||
}
|
}
|
||||||
|
|
@ -55,15 +70,4 @@ impl Element for ForeignObject {
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.atts
|
&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()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn group() -> Group {
|
||||||
Group {
|
Group::default()
|
||||||
children: Vec::new(),
|
|
||||||
atts: vec![],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct Group {
|
pub struct Group {
|
||||||
children: Vec<Box<dyn Element>>,
|
children: Vec<Box<dyn SvgElement>>,
|
||||||
atts: Vec<Att>,
|
atts: Vec<Att>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Group {
|
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));
|
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 {
|
fn get_type(&self) -> ElementType {
|
||||||
ElementType::Group(Vec::new())
|
ElementType::Group(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
@ -34,19 +47,6 @@ impl Element for Group {
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.atts
|
&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)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn image() -> Image {
|
||||||
Image::default()
|
Image::default()
|
||||||
|
|
@ -39,17 +40,20 @@ impl Image {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Image {
|
impl Display for Image {
|
||||||
fn get_type(&self) -> ElementType {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
ElementType::Image
|
write!(
|
||||||
}
|
f,
|
||||||
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
format!(
|
|
||||||
r#"<image{} />"#,
|
r#"<image{} />"#,
|
||||||
self.atts.iter().map(att_to_string).collect::<String>()
|
self.atts.iter().map(att_to_string).collect::<String>()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SvgElement for Image {
|
||||||
|
fn get_type(&self) -> ElementType {
|
||||||
|
ElementType::Image
|
||||||
|
}
|
||||||
|
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.atts
|
&self.atts
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn line() -> Line {
|
||||||
Line(vec![])
|
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 {
|
fn get_type(&self) -> ElementType {
|
||||||
ElementType::Line
|
ElementType::Line
|
||||||
}
|
}
|
||||||
|
|
@ -42,13 +53,6 @@ impl Element for Line {
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
format!(
|
|
||||||
r#" <line{} />"#,
|
|
||||||
self.0.iter().map(att_to_string).collect::<String>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shape for Line {
|
impl Shape for Line {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn link(href: &str) -> Link {
|
||||||
Link(vec![att("href", href)])
|
Link(vec![att("href", href)])
|
||||||
|
|
@ -12,17 +13,20 @@ impl Link {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Link {
|
impl Display for Link {
|
||||||
fn get_type(&self) -> ElementType {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
ElementType::Link
|
write!(
|
||||||
}
|
f,
|
||||||
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
format!(
|
|
||||||
r#"<link{} />"#,
|
r#"<link{} />"#,
|
||||||
self.0.iter().map(att_to_string).collect::<String>()
|
self.0.iter().map(att_to_string).collect::<String>()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SvgElement for Link {
|
||||||
|
fn get_type(&self) -> ElementType {
|
||||||
|
ElementType::Link
|
||||||
|
}
|
||||||
|
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.0
|
&self.0
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,8 @@ pub enum ElementType {
|
||||||
Rect,
|
Rect,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Element {
|
pub trait SvgElement: Display {
|
||||||
fn get_type(&self) -> ElementType;
|
fn get_type(&self) -> ElementType;
|
||||||
fn to_string(&self) -> String;
|
|
||||||
fn atts(&self) -> &[Att];
|
fn atts(&self) -> &[Att];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn path(d: &str) -> Path {
|
||||||
Path::new(d)
|
Path::new(d)
|
||||||
|
|
@ -9,15 +10,22 @@ pub struct Path {
|
||||||
atts: Vec<Att>,
|
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 {
|
fn get_type(&self) -> ElementType {
|
||||||
ElementType::Path
|
ElementType::Path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.atts
|
&self.atts
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn rect() -> Rect {
|
||||||
Rect::default()
|
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 {
|
fn get_type(&self) -> ElementType {
|
||||||
ElementType::Rect
|
ElementType::Rect
|
||||||
}
|
}
|
||||||
|
|
@ -68,13 +78,6 @@ impl Element for Rect {
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.atts
|
&self.atts
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
format!(
|
|
||||||
r#"<rect{} />"#,
|
|
||||||
self.atts.iter().map(att_to_string).collect::<String>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
use std::fmt;
|
||||||
|
|
||||||
pub fn svg() -> Svg {
|
pub fn svg() -> Svg {
|
||||||
|
|
@ -8,7 +8,7 @@ pub fn svg() -> Svg {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Svg {
|
pub struct Svg {
|
||||||
style: Option<String>,
|
style: Option<String>,
|
||||||
elements: Vec<Box<dyn Element>>,
|
elements: Vec<Box<dyn SvgElement>>,
|
||||||
atts: Vec<Att>,
|
atts: Vec<Att>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ impl Svg {
|
||||||
self.style = Some(style.to_string());
|
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));
|
self.elements.push(Box::new(child));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ impl Svg {
|
||||||
|
|
||||||
pub fn preserveaspectratio<V: Into<Value>>(&mut self, preserveaspectratio: V) {
|
pub fn preserveaspectratio<V: Into<Value>>(&mut self, preserveaspectratio: V) {
|
||||||
self.atts
|
self.atts
|
||||||
.push(att("preserveaspectratio", preserveaspectratio));
|
.push(att("preserveAspectRatio", preserveaspectratio));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform<V: Into<Value>>(&mut self, transform: V) {
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
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()
|
||||||
|
|
@ -83,7 +83,7 @@ mod tests {
|
||||||
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!(
|
||||||
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()
|
svg.to_string()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +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::Write;
|
use std::fmt::{Display, Write};
|
||||||
|
|
||||||
pub fn text() -> Text {
|
pub fn text() -> Text {
|
||||||
Text::default()
|
Text::default()
|
||||||
|
|
@ -74,16 +74,11 @@ impl Text {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Text {
|
impl Display for Text {
|
||||||
fn get_type(&self) -> ElementType {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
ElementType::Rect
|
write!(
|
||||||
}
|
f,
|
||||||
|
r#"<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();
|
|
||||||
format!(
|
|
||||||
r#"<text{}>{}</text>"#,
|
|
||||||
self.atts.iter().map(att_to_string).collect::<String>(),
|
self.atts.iter().map(att_to_string).collect::<String>(),
|
||||||
self.text.lines().fold(String::new(), |mut output, l| {
|
self.text.lines().fold(String::new(), |mut output, l| {
|
||||||
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();
|
||||||
|
|
@ -93,6 +88,12 @@ impl Element for Text {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SvgElement for Text {
|
||||||
|
fn get_type(&self) -> ElementType {
|
||||||
|
ElementType::Rect
|
||||||
|
}
|
||||||
|
|
||||||
fn atts(&self) -> &[Att] {
|
fn atts(&self) -> &[Att] {
|
||||||
&self.atts
|
&self.atts
|
||||||
|
|
@ -105,10 +106,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rect() {
|
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!(
|
assert_eq!(
|
||||||
r#"<rect x="0" y="0" width="10" height="10" />"#,
|
r#"<text x="0" y="0" width="10" height="10" />"#,
|
||||||
rect.to_string()
|
text.to_string()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue