Compare commits
No commits in common. "921df96c955be25f77cbd660832278d50a580a74" and "92bab910203495666c6bbb4504fc1286a8278b94" have entirely different histories.
921df96c95
...
92bab91020
29 changed files with 264 additions and 1956 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
|
@ -8,18 +8,6 @@ version = "1.0.95"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
|
|
@ -31,7 +19,5 @@ name = "vis"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"once_cell",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -6,5 +6,3 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
unicode-segmentation = "1.1"
|
||||
once_cell = "1.20.2"
|
||||
log = "0.4"
|
||||
40
README.md
40
README.md
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
_It is Dutch for fish_
|
||||
|
||||
### NB I just started, needs a lot of work
|
||||
|
||||
sample vis file:
|
||||
|
||||
```
|
||||
structure {
|
||||
markup {
|
||||
lanes {
|
||||
functions {
|
||||
calc: "Calculation"
|
||||
|
|
@ -30,23 +32,39 @@ 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 {
|
||||
lanes(group) {
|
||||
type: textnode
|
||||
orientation: vertical
|
||||
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) {orientation: horizontal}
|
||||
systems(group) {orientation: horizontal}
|
||||
}
|
||||
```
|
||||
|
||||
#### Output (work in progress):
|
||||
|
||||

|
||||
Will have to be turned into an architecture diagram... we'll see how it goes!
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="400" viewBox="0 0 980 255">
|
||||
<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="60" y="60" width="61" height="15" stroke="white" fill="none"/>
|
||||
<text x="90" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="90" dy="10">Calculation</tspan>
|
||||
</text>
|
||||
<rect id="acc_interest_calc" x="136" y="60" width="145" height="15" stroke="white" fill="none"/>
|
||||
<text x="208" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="208" dy="10">Account interest Calculation</tspan>
|
||||
</text>
|
||||
<rect id="interest_rates" x="296" y="60" width="76" height="15" stroke="white" fill="none"/>
|
||||
<text x="334" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="334" dy="10">Interest Rates</tspan>
|
||||
</text>
|
||||
<rect id="config" x="387" y="60" width="71" height="15" stroke="white" fill="none"/>
|
||||
<text x="422" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="422" dy="10">Configuration</tspan>
|
||||
</text>
|
||||
<rect id="nob_execution" x="488" y="75" width="71" height="15" stroke="white" fill="none"/>
|
||||
<text x="523" y="75" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="523" dy="10">NoB Execution</tspan>
|
||||
</text>
|
||||
<rect id="coll_reinst_inst" x="574" y="75" width="204" height="15" stroke="white" fill="none"/>
|
||||
<text x="676" y="75" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="676" dy="10">Collection of Reinstatement instructions</tspan>
|
||||
</text>
|
||||
<rect id="nob" x="473" y="60" width="320" height="45" stroke="white" fill="none"/>
|
||||
<text x="633" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="633" dy="10">NoB</tspan>
|
||||
</text>
|
||||
<rect id="reporting" x="808" y="60" width="52" height="15" stroke="white" fill="none"/>
|
||||
<text x="834" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="834" dy="10">Reporting</tspan>
|
||||
</text>
|
||||
<rect id="functions" x="45" y="45" width="830" height="75" stroke="white" fill="none"/>
|
||||
<text x="460" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"></text>
|
||||
<rect id="bank_motor" x="75" y="165" width="57" height="15" stroke="white" fill="none"/>
|
||||
<text x="103" y="165" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="103" dy="10">Bank Motor</tspan>
|
||||
</text>
|
||||
<rect id="bank_scripts" x="147" y="165" width="66" height="15" stroke="white" fill="none"/>
|
||||
<text x="180" y="165" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="180" dy="10">Bank Scripts</tspan>
|
||||
</text>
|
||||
<rect id="bank_client" x="228" y="165" width="61" height="15" stroke="white" fill="none"/>
|
||||
<text x="258" y="165" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="258" dy="10">Bank Client</tspan>
|
||||
</text>
|
||||
<rect id="bank_db" x="304" y="165" width="42" height="15" stroke="white" fill="none"/>
|
||||
<text x="325" y="165" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="325" dy="10">Bank DB</tspan>
|
||||
</text>
|
||||
<rect id="bank" x="60" y="150" width="301" height="45" stroke="white" fill="none"/>
|
||||
<text x="210" y="150" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="210" dy="10">Bank</tspan>
|
||||
</text>
|
||||
<rect id="interest_engine" x="376" y="150" width="76" height="15" stroke="white" fill="none"/>
|
||||
<text x="414" y="150" fill="white" text-anchor="middle" stroke-width="1" class="node">
|
||||
<tspan x="414" dy="10">InterestEngine</tspan>
|
||||
</text>
|
||||
<rect id="systems" x="45" y="135" width="422" height="75" stroke="white" fill="none"/>
|
||||
<text x="256" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"></text>
|
||||
<rect id="lanes" x="30" y="30" width="860" height="195" stroke="white" fill="none"/>
|
||||
<text x="460" y="30" fill="white" text-anchor="middle" stroke-width="1" class="node"></text>
|
||||
<rect id="bank" x="905" y="30" width="0" height="0" stroke="white" fill="none"/>
|
||||
<text x="905" y="30" fill="white" text-anchor="middle" stroke-width="1" class="node"></text>
|
||||
<rect id="bank_scripts" x="920" y="30" width="0" height="0" stroke="white" fill="none"/>
|
||||
<text x="920" y="30" fill="white" text-anchor="middle" stroke-width="1" class="node"></text>
|
||||
<rect id="bank_motor" x="935" y="30" width="0" height="0" stroke="white" fill="none"/>
|
||||
<text x="935" y="30" fill="white" text-anchor="middle" stroke-width="1" class="node"></text>
|
||||
<rect id="interest_engine" x="950" y="30" width="0" height="0" stroke="white" fill="none"/>
|
||||
<text x="950" y="30" fill="white" text-anchor="middle" stroke-width="1" class="node"></text>
|
||||
<rect id="structure" x="15" y="15" width="950" height="225" stroke="white" fill="none"/>
|
||||
<text x="490" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node"></text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
|
|
@ -21,18 +21,36 @@ 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 {
|
||||
lanes(group) {
|
||||
type: textnode
|
||||
orientation: vertical
|
||||
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) {orientation: horizontal}
|
||||
systems(group) {orientation: horizontal}
|
||||
}
|
||||
|
|
|
|||
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
216
src/lib.rs
216
src/lib.rs
|
|
@ -1,220 +1,32 @@
|
|||
pub mod parse;
|
||||
pub mod render;
|
||||
|
||||
use crate::parse::tokens::TokenType;
|
||||
use log::debug;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
pub const BODY: &str = "structure";
|
||||
pub const DEFAULT_ORIENTATION: &str = "horizontal";
|
||||
pub const ORIENTATION: &str = "orientation";
|
||||
pub const HORIZONTAL: &str = "horizontal";
|
||||
pub const VERTICAL: &str = "vertical";
|
||||
use tokens::TokenType;
|
||||
|
||||
pub mod parser;
|
||||
mod scanner;
|
||||
mod tokens;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Vis {
|
||||
pub structure: Vec<VisNode>,
|
||||
pub structure: Vec<Element>,
|
||||
pub styles: Vec<StyleNode>,
|
||||
}
|
||||
|
||||
impl Vis {
|
||||
pub fn new(structure: VisNode, styles: Vec<StyleNode>) -> Self {
|
||||
let mut vis = Self {
|
||||
structure: vec![structure],
|
||||
styles,
|
||||
};
|
||||
|
||||
// set defaults
|
||||
let bodystyle = vis.get_style_node_mut(BODY);
|
||||
if let Some(bodystyle) = bodystyle {
|
||||
bodystyle.set_attribute_if_absent(ORIENTATION, DEFAULT_ORIENTATION);
|
||||
} else {
|
||||
let mut bodystyle = StyleNode::new(BODY, ContainerType::Group);
|
||||
bodystyle.set_attribute(ORIENTATION, DEFAULT_ORIENTATION);
|
||||
vis.styles.push(bodystyle);
|
||||
#[derive(Debug)]
|
||||
pub enum Element {
|
||||
Node(String, Option<String>, Vec<Element>),
|
||||
Edge(String, String, TokenType, Option<String>),
|
||||
}
|
||||
|
||||
vis
|
||||
}
|
||||
|
||||
pub fn get_node(&self, id: impl Into<String>) -> Option<&VisNode> {
|
||||
Self::get_node_recurse(id, &self.structure)
|
||||
}
|
||||
|
||||
fn get_node_recurse(id: impl Into<String>, elements: &Vec<VisNode>) -> Option<&VisNode> {
|
||||
let id = id.into();
|
||||
for element in elements {
|
||||
if let Some(e) = Self::get_node_recurse(id.clone(), &element.children) {
|
||||
return Some(e);
|
||||
} else if element.id == id {
|
||||
return Some(element);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_style_node_mut(&mut self, id_ref: &str) -> Option<&mut StyleNode> {
|
||||
self.styles.iter_mut().find(|s| s.id_ref == id_ref)
|
||||
}
|
||||
|
||||
pub fn get_style_value(&self, idref: impl Into<String>, key: &str) -> Option<String> {
|
||||
self.get_styles(idref).get(key).map(String::to_string)
|
||||
}
|
||||
|
||||
pub fn get_styles(&self, id: impl Into<String>) -> HashMap<String, String> {
|
||||
// println!("get_styles {:?}", id);
|
||||
let mut styles = HashMap::new();
|
||||
self.get_styles_recurse(id, &mut styles);
|
||||
debug!("found {:?}", styles);
|
||||
styles
|
||||
}
|
||||
|
||||
fn get_styles_recurse(&self, id: impl Into<String>, styles: &mut HashMap<String, String>) {
|
||||
let id = id.into();
|
||||
debug!("get style for {}", id);
|
||||
let node = self.get_node(id);
|
||||
if let Some(node) = node {
|
||||
// println!("node {:?}", node);
|
||||
let style = self.styles.iter().find(|s| s.id_ref == node.id);
|
||||
if let Some(style) = style {
|
||||
style.attributes.iter().for_each(|(k, v)| {
|
||||
if !styles.contains_key(k) {
|
||||
styles.insert(k.clone(), v.clone());
|
||||
};
|
||||
});
|
||||
}
|
||||
if let Some(parent) = &node.parent {
|
||||
self.get_styles_recurse(parent, styles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum NodeType {
|
||||
Node,
|
||||
Edge,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
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 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(
|
||||
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 VisNode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.node_type {
|
||||
NodeType::Node => {
|
||||
let mut string = String::new();
|
||||
string.push_str(&format!(
|
||||
"Node {{{}: {}",
|
||||
self.id,
|
||||
self.label.as_ref().unwrap_or(&"".to_string())
|
||||
));
|
||||
for child in &self.children {
|
||||
string.push_str(&format!(" {}", child));
|
||||
}
|
||||
string.push('}');
|
||||
write!(f, "{}", string)
|
||||
}
|
||||
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)?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct StyleNode {
|
||||
pub id_ref: String,
|
||||
pub containertype: ContainerType,
|
||||
pub attributes: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl StyleNode {
|
||||
fn new(id_ref: impl Into<String>, containertype: ContainerType) -> Self {
|
||||
Self {
|
||||
id_ref: id_ref.into(),
|
||||
containertype,
|
||||
attributes: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_attribute(&self, key: &str) -> bool {
|
||||
self.attributes.contains_key(key)
|
||||
}
|
||||
pub fn get_attribute(&self, key: &str) -> Option<&String> {
|
||||
self.attributes.get(key)
|
||||
}
|
||||
pub fn get_attribute_string(&self, key: &str) -> Option<String> {
|
||||
self.attributes.get(key).map(|s| s.to_owned())
|
||||
}
|
||||
pub fn set_attribute(&mut self, key: impl Into<String>, value: impl Into<String>) {
|
||||
self.attributes.insert(key.into(), value.into());
|
||||
}
|
||||
|
||||
pub fn set_attribute_if_absent(&mut self, key: impl Into<String>, value: impl Into<String>) {
|
||||
let key = key.into();
|
||||
if !self.has_attribute(&key) {
|
||||
self.set_attribute(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum ContainerType {
|
||||
NonGroup, // needs thinking about
|
||||
Group, // the idea is that group nodes don't have style, they just bequeath it to their children
|
||||
Node,
|
||||
Group,
|
||||
}
|
||||
|
|
|
|||
15
src/main.rs
15
src/main.rs
|
|
@ -1,24 +1,15 @@
|
|||
use anyhow::anyhow;
|
||||
use std::env::args;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::process::exit;
|
||||
use vis::render::svg_renderer::SvgRender;
|
||||
use vis::render::Renderer;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args: Vec<String> = args().collect();
|
||||
if args.len() != 2 {
|
||||
eprintln!("Usage: vis vis-file");
|
||||
exit(-64);
|
||||
return Err(anyhow!("Usage: vis vis-file"));
|
||||
} else {
|
||||
let vis_file = read_file(&args[1])?;
|
||||
let vis = vis::parse::parse_vis(vis_file.as_str())?;
|
||||
// println!("{:?}", vis);
|
||||
let svg_bytes = SvgRender {}.render(vis)?;
|
||||
let mut file = File::create("bank.svg").expect("Unable to create file");
|
||||
file.write_all(&svg_bytes).expect("Unable to write data");
|
||||
let vis = vis::parser::parse_vis(vis_file.as_str())?;
|
||||
println!("{:?}", vis);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
mod scanner;
|
||||
pub mod tokens;
|
||||
pub mod parser;
|
||||
|
||||
pub use parser::parse_vis;
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
use crate::{
|
||||
parse::tokens::{
|
||||
Token,
|
||||
TokenType::{self, *},
|
||||
},
|
||||
ContainerType, StyleNode, Vis, VisNode,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use log::debug;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn parse_vis(contents: &str) -> anyhow::Result<Vis> {
|
||||
let tokens = crate::parse::scanner::scan(contents)?;
|
||||
debug!("{:?}", tokens);
|
||||
let mut parser = Parser::new(tokens);
|
||||
|
||||
let structure = parser.structure()?;
|
||||
let styles = parser.styles()?;
|
||||
debug!("parsed styles{:?}", styles);
|
||||
let mut vis = Vis::new(
|
||||
VisNode::new_node("structure", None::<String>, structure),
|
||||
styles,
|
||||
);
|
||||
|
||||
// add bottom up references
|
||||
vis.structure.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 {
|
||||
tokens: Vec<Token>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: Vec<Token>) -> Self {
|
||||
Self { tokens, current: 0 }
|
||||
}
|
||||
|
||||
fn structure(&mut self) -> anyhow::Result<Vec<VisNode>> {
|
||||
if self.match_token(Structure) {
|
||||
self.elements()
|
||||
} else {
|
||||
println!("No structure found");
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
fn elements(&mut self) -> anyhow::Result<Vec<VisNode>> {
|
||||
debug!("nodes {:?}", self.peek());
|
||||
self.consume(LeftBrace, "Expected '{'")?;
|
||||
let mut nodes = vec![];
|
||||
while !self.match_token(RightBrace) {
|
||||
nodes.push(self.element()?);
|
||||
}
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
fn element(&mut self) -> anyhow::Result<VisNode> {
|
||||
let id = self.id()?;
|
||||
let current = self.peek().clone();
|
||||
if self.match_tokens(vec![
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
DiamondArrowRight,
|
||||
DiamondArrowLeft,
|
||||
]) {
|
||||
self.edge(id, current)
|
||||
} else {
|
||||
let title = self.title()?;
|
||||
let children = if self.check(&LeftBrace) {
|
||||
self.elements()?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
Ok(VisNode::new_node(id, title, children))
|
||||
}
|
||||
}
|
||||
|
||||
fn edge(&mut self, from_id: String, arrow: Token) -> anyhow::Result<VisNode> {
|
||||
let to_id = self.id()?;
|
||||
let title = self.title()?;
|
||||
Ok(VisNode::new_edge(from_id, to_id, arrow.tokentype, title))
|
||||
}
|
||||
|
||||
fn title(&mut self) -> anyhow::Result<Option<String>> {
|
||||
if self.check(&Colon) {
|
||||
self.advance();
|
||||
Ok(Some(self.string()?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&mut self) -> anyhow::Result<String> {
|
||||
self.text()
|
||||
}
|
||||
|
||||
fn string(&mut self) -> anyhow::Result<String> {
|
||||
let text = self.peek().clone();
|
||||
let text = strip_surrounding(text.lexeme);
|
||||
self.consume(Str, "Expected quoted string")?;
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
fn text(&mut self) -> anyhow::Result<String> {
|
||||
let text = self.peek().clone();
|
||||
self.consume(Identifier, "Expected text")?;
|
||||
Ok(text.lexeme.to_owned())
|
||||
}
|
||||
|
||||
fn styles(&mut self) -> anyhow::Result<Vec<StyleNode>> {
|
||||
debug!("styles {:?}", self.peek());
|
||||
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<StyleNode> {
|
||||
debug!("style {:?}", self.peek());
|
||||
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()
|
||||
};
|
||||
debug!("idref {:?}", idref);
|
||||
self.advance();
|
||||
let containertype = self.containertype()?;
|
||||
if self.peek().tokentype == Colon {
|
||||
// optional
|
||||
self.advance();
|
||||
}
|
||||
self.consume(LeftBrace, "Expected '{'")?;
|
||||
debug!("attributes {:?}", self.peek());
|
||||
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<HashMap<String, String>> {
|
||||
debug!("read attributes {:?}", self.peek());
|
||||
let mut elements = HashMap::new();
|
||||
let mut key = self.peek().clone();
|
||||
while key.tokentype != RightBrace {
|
||||
self.advance();
|
||||
self.consume(Colon, "Expected ':'")?;
|
||||
let value = self.advance().clone();
|
||||
elements.insert(key.lexeme.to_owned(), value.lexeme);
|
||||
key = self.peek().clone();
|
||||
}
|
||||
Ok(elements)
|
||||
}
|
||||
|
||||
fn containertype(&mut self) -> anyhow::Result<ContainerType> {
|
||||
Ok(if self.check(&LeftParen) {
|
||||
self.advance();
|
||||
if self.match_token(Group) {
|
||||
self.consume(RightParen, "Expected ')'")?;
|
||||
ContainerType::Group
|
||||
} else {
|
||||
self.consume(RightParen, "Expected ')'")?;
|
||||
ContainerType::NonGroup
|
||||
}
|
||||
} else {
|
||||
ContainerType::NonGroup
|
||||
})
|
||||
}
|
||||
|
||||
fn consume(&mut self, tokentype: TokenType, expect: &str) -> anyhow::Result<&Token> {
|
||||
let current = self.peek();
|
||||
if self.check(&tokentype) {
|
||||
Ok(self.advance())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Error: {} but was '{}' on line {}",
|
||||
expect,
|
||||
self.peek().lexeme,
|
||||
current.line
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn match_tokens(&mut self, tokentypes: Vec<TokenType>) -> bool {
|
||||
for tokentype in tokentypes.iter() {
|
||||
if self.check(tokentype) {
|
||||
self.advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn match_token(&mut self, tokentype: TokenType) -> bool {
|
||||
if self.check(&tokentype) {
|
||||
self.advance();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn check(&self, tokentype: &TokenType) -> bool {
|
||||
if self.is_at_end() {
|
||||
false
|
||||
} else {
|
||||
&self.peek().tokentype == tokentype
|
||||
}
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> &Token {
|
||||
if !self.is_at_end() {
|
||||
self.current += 1;
|
||||
}
|
||||
self.previous()
|
||||
}
|
||||
|
||||
fn previous(&self) -> &Token {
|
||||
&self.tokens[self.current - 1]
|
||||
}
|
||||
|
||||
fn is_at_end(&self) -> bool {
|
||||
self.peek().tokentype == TokenType::Eof
|
||||
}
|
||||
|
||||
fn peek(&self) -> &Token {
|
||||
&self.tokens[self.current]
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_surrounding(s: String) -> String {
|
||||
s[1..s.len() - 1].to_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::NodeType::Node;
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
let vis_source = r#"
|
||||
structure {
|
||||
top: "top-node" {
|
||||
child: "child-node" {
|
||||
}
|
||||
}
|
||||
}
|
||||
styles {
|
||||
structure {
|
||||
adam: eve
|
||||
orientation: horizontal;
|
||||
}
|
||||
top(group) {
|
||||
orientation: vertical;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let vis = crate::parse::parser::parse_vis(vis_source).ok();
|
||||
|
||||
assert!(vis.is_some(), "Parsed structure should not be None");
|
||||
|
||||
if let Some(vis) = vis {
|
||||
assert_eq!(
|
||||
vis.structure[0].children.len(),
|
||||
1,
|
||||
"The structure should contain one top-level element"
|
||||
);
|
||||
|
||||
if vis.structure[0].children[0].node_type == Node {
|
||||
let top = &vis.structure[0].children[0];
|
||||
assert_eq!(top.id, "top", "The ID of the first node should be 'top'");
|
||||
assert_eq!(
|
||||
top.label.as_ref().unwrap(),
|
||||
&"top-node".to_owned(),
|
||||
"The title of the first node should be 'top-node'"
|
||||
);
|
||||
assert_eq!(top.children.len(), 1);
|
||||
let child = &top.children[0];
|
||||
if child.node_type == Node {
|
||||
assert_eq!(
|
||||
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!(child.children.len(), 0);
|
||||
}
|
||||
} else {
|
||||
panic!("The top-level element should be a Node");
|
||||
}
|
||||
assert_eq!(vis.styles.len(), 2);
|
||||
let styles = vis.get_styles("top");
|
||||
assert_eq!(styles.len(), 2);
|
||||
assert_eq!(styles["orientation"], "vertical"); // overrides parent style
|
||||
} else {
|
||||
panic!("Parsed structure was None");
|
||||
}
|
||||
}
|
||||
}
|
||||
163
src/parser.rs
Normal file
163
src/parser.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
use crate::{
|
||||
tokens::{
|
||||
Token,
|
||||
TokenType::{self, *},
|
||||
},
|
||||
Element, StyleNode, Vis,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
|
||||
pub fn parse_vis(contents: &str) -> anyhow::Result<Vis> {
|
||||
let tokens = crate::scanner::scan(contents)?;
|
||||
// println!("{:?}", tokens);
|
||||
let mut parser = Parser::new(tokens);
|
||||
|
||||
Ok(Vis {
|
||||
structure: parser.structure()?,
|
||||
styles: parser.styles()?,
|
||||
})
|
||||
}
|
||||
|
||||
struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: Vec<Token>) -> Self {
|
||||
Self { tokens, current: 0 }
|
||||
}
|
||||
|
||||
fn structure(&mut self) -> anyhow::Result<Vec<Element>> {
|
||||
if self.match_token(Structure) {
|
||||
self.elements()
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
fn elements(&mut self) -> anyhow::Result<Vec<Element>> {
|
||||
// println!("nodes {:?}", self.peek());
|
||||
self.consume(LeftBrace, "Expected '{'")?;
|
||||
let mut nodes = vec![];
|
||||
while !self.match_token(RightBrace) {
|
||||
nodes.push(self.element()?);
|
||||
}
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
fn element(&mut self) -> anyhow::Result<Element> {
|
||||
// println!("node {:?}", self.peek());
|
||||
let id = self.id()?;
|
||||
// println!("id {}", id);
|
||||
let current = self.peek().clone();
|
||||
if self.match_tokens(vec![
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
DiamondArrowRight,
|
||||
DiamondArrowLeft,
|
||||
]) {
|
||||
self.edge(id, current)
|
||||
} else {
|
||||
let title = self.title()?;
|
||||
// println!("title {:?}", title);
|
||||
let children = if self.check(&LeftBrace) {
|
||||
self.elements()?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
Ok(Element::Node(id, title, children))
|
||||
}
|
||||
}
|
||||
|
||||
fn edge(&mut self, from_id: String, arrow: Token) -> anyhow::Result<Element> {
|
||||
let to_id = self.id()?;
|
||||
let title = self.title()?;
|
||||
Ok(Element::Edge(from_id, to_id, arrow.tokentype, title))
|
||||
}
|
||||
|
||||
fn title(&mut self) -> anyhow::Result<Option<String>> {
|
||||
if self.check(&Colon) {
|
||||
self.advance();
|
||||
Ok(Some(self.string()?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&mut self) -> anyhow::Result<String> {
|
||||
self.text()
|
||||
}
|
||||
|
||||
fn string(&mut self) -> anyhow::Result<String> {
|
||||
let text = self.peek().clone();
|
||||
self.consume(Str, "Expected quoted string")?;
|
||||
Ok(text.lexeme.to_owned())
|
||||
}
|
||||
|
||||
fn text(&mut self) -> anyhow::Result<String> {
|
||||
let text = self.peek().clone();
|
||||
self.consume(Identifier, "Expected text")?;
|
||||
Ok(text.lexeme.to_owned())
|
||||
}
|
||||
|
||||
fn styles(&mut self) -> anyhow::Result<Vec<StyleNode>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn consume(&mut self, tokentype: TokenType, expect: &str) -> anyhow::Result<&Token> {
|
||||
let current = self.peek();
|
||||
if self.check(&tokentype) {
|
||||
Ok(self.advance())
|
||||
} else {
|
||||
Err(anyhow!("Error: {} on line {}", expect, current.line))
|
||||
}
|
||||
}
|
||||
|
||||
fn match_tokens(&mut self, tokentypes: Vec<TokenType>) -> bool {
|
||||
for tokentype in tokentypes.iter() {
|
||||
if self.check(tokentype) {
|
||||
self.advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn match_token(&mut self, tokentype: TokenType) -> bool {
|
||||
if self.check(&tokentype) {
|
||||
self.advance();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn check(&self, tokentype: &TokenType) -> bool {
|
||||
if self.is_at_end() {
|
||||
false
|
||||
} else {
|
||||
&self.peek().tokentype == tokentype
|
||||
}
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> &Token {
|
||||
if !self.is_at_end() {
|
||||
self.current += 1;
|
||||
}
|
||||
self.previous()
|
||||
}
|
||||
|
||||
fn previous(&self) -> &Token {
|
||||
&self.tokens[self.current - 1]
|
||||
}
|
||||
|
||||
fn is_at_end(&self) -> bool {
|
||||
self.peek().tokentype == TokenType::Eof
|
||||
}
|
||||
|
||||
fn peek(&self) -> &Token {
|
||||
&self.tokens[self.current]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
use crate::Vis;
|
||||
|
||||
pub mod svg_renderer;
|
||||
pub mod svglib;
|
||||
|
||||
/// trait for turning the object model into a byte representation
|
||||
pub trait Renderer {
|
||||
fn render(&self, vis: Vis) -> anyhow::Result<Vec<u8>>;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
use crate::render::svglib::rect::rect;
|
||||
use crate::render::svglib::svg::{svg, Svg};
|
||||
use crate::render::svglib::text::text;
|
||||
use crate::render::Renderer;
|
||||
use crate::{StyleNode, Vis, VisNode, BODY, HORIZONTAL, ORIENTATION};
|
||||
use log::debug;
|
||||
|
||||
pub struct SvgRender;
|
||||
|
||||
impl Renderer for SvgRender {
|
||||
fn render(&self, vis: Vis) -> anyhow::Result<Vec<u8>> {
|
||||
let mut svg = svg();
|
||||
|
||||
svg.width(100);
|
||||
svg.height(100);
|
||||
let style = include_str!("svg_node.css");
|
||||
svg.style(style);
|
||||
let (width, height) =
|
||||
render_elements(&vis, &mut svg, BODY, &vis.structure, &vis.styles, 0, 0);
|
||||
svg.viewbox(format!("0 0 {} {}", width, height));
|
||||
Ok(svg.to_string().into_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
fn render_elements(
|
||||
vis: &Vis,
|
||||
svg: &mut Svg,
|
||||
parent: &str,
|
||||
elements: &[VisNode],
|
||||
_styles: &Vec<StyleNode>,
|
||||
start_x: u32,
|
||||
start_y: u32,
|
||||
) -> (u32, u32) {
|
||||
let padding = 15;
|
||||
|
||||
let mut total_width = 0;
|
||||
let mut total_height = 0;
|
||||
let mut x = start_x;
|
||||
let mut y = start_y;
|
||||
|
||||
for element in elements {
|
||||
let (width, height) = if !element.children.is_empty() {
|
||||
render_elements(
|
||||
vis,
|
||||
svg,
|
||||
element.id.as_str(),
|
||||
&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)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
debug!(
|
||||
"{}:{:?}",
|
||||
&element.id,
|
||||
vis.get_style_value(&element.id, ORIENTATION)
|
||||
);
|
||||
if is_horizontal_orientation(vis, parent) {
|
||||
total_width += width + padding;
|
||||
total_height = u32::max(total_height, height + padding);
|
||||
} else {
|
||||
total_width = u32::max(total_width, width + padding);
|
||||
total_height += height + padding;
|
||||
}
|
||||
|
||||
svg.add(
|
||||
rect()
|
||||
.id(element.id.clone())
|
||||
.x(x + padding)
|
||||
.y(y + padding)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.stroke("white")
|
||||
.fill("none"),
|
||||
);
|
||||
|
||||
svg.add(
|
||||
text()
|
||||
.x(x + padding + width / 2)
|
||||
.y(start_y + padding)
|
||||
.fill("white")
|
||||
.text(element.label.as_ref().unwrap_or(&"".to_string()))
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("stroke-width", "1")
|
||||
.class("node"),
|
||||
);
|
||||
if is_horizontal_orientation(vis, parent) {
|
||||
x += width + padding;
|
||||
} else {
|
||||
y += height + padding;
|
||||
}
|
||||
}
|
||||
|
||||
(total_width + padding, total_height + padding)
|
||||
}
|
||||
|
||||
fn is_horizontal_orientation(vis: &Vis, id: impl Into<String>) -> bool {
|
||||
let hor = vis.get_style_value(id, ORIENTATION);
|
||||
let hor = hor.as_deref();
|
||||
hor == Some(HORIZONTAL) || hor == None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{ContainerType, StyleNode, Vis, VisNode};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn test_render_vis_with_grid_layout() {
|
||||
// Create a mock StyleNode
|
||||
let style_node = StyleNode {
|
||||
id_ref: "node_1".to_string(),
|
||||
containertype: ContainerType::NonGroup,
|
||||
attributes: [("fill".to_string(), "white".to_string())]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
// Create mock Elements
|
||||
let element1 = VisNode::new_node(
|
||||
"node1",
|
||||
Some("Node 1\nlonger"),
|
||||
vec![], // No child elements
|
||||
);
|
||||
let element2 = VisNode::new_node(
|
||||
"node2",
|
||||
Some("Node 2"),
|
||||
vec![], // No child elements
|
||||
);
|
||||
|
||||
let element3 = VisNode::new_node(
|
||||
"node3",
|
||||
Some("Node 3\nis longer\nthan you are to me"),
|
||||
vec![], // No child elements
|
||||
);
|
||||
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],
|
||||
};
|
||||
|
||||
let svg_output = SvgRender {}.render(vis).unwrap();
|
||||
|
||||
let mut file = File::create("output_multiple_nodes.svg").expect("Unable to create file");
|
||||
file.write_all(&svg_output).expect("Unable to write data");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, SvgElement, ElementType, Shape, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn circle() -> Circle {
|
||||
Circle::default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Circle(Vec<Att>);
|
||||
|
||||
impl Circle {
|
||||
pub fn id<V: Into<Value>>(mut self, id: V) -> Self {
|
||||
self.0.push(att("id", id));
|
||||
self
|
||||
}
|
||||
pub fn cx<V: Into<Value>>(mut self, cx: V) -> Self {
|
||||
self.0.push(att("cx", cx));
|
||||
self
|
||||
}
|
||||
pub fn cy<V: Into<Value>>(mut self, cy: V) -> Self {
|
||||
self.0.push(att("cy", cy));
|
||||
self
|
||||
}
|
||||
pub fn r<V: Into<Value>>(mut self, r: V) -> Self {
|
||||
self.0.push(att("r", r));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape for Circle {
|
||||
fn fill<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.0.push(att("fill", value));
|
||||
self
|
||||
}
|
||||
|
||||
fn stroke<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.0.push(att("stroke", value));
|
||||
self
|
||||
}
|
||||
|
||||
fn transform<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.0.push(att("transform", value));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[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 cx="1em" cy="0" r="10" id="c" />"#,
|
||||
circle.to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Value};
|
||||
use std::fmt;
|
||||
pub fn div() -> Div {
|
||||
Div::default()
|
||||
}
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Div {
|
||||
atts: Vec<Att>,
|
||||
child: String,
|
||||
}
|
||||
|
||||
impl Div {
|
||||
pub fn id<V: Into<Value>>(&mut self, id: V) {
|
||||
self.atts.push(att("id", id));
|
||||
}
|
||||
|
||||
pub fn class<V>(mut self, class: V) -> Self
|
||||
where
|
||||
V: Into<Value>,
|
||||
{
|
||||
self.atts.push(att("class", class));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn inner_html<V: Into<String>>(mut self, html: V) -> Self {
|
||||
self.child = html.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Div {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<div xmlns="http://www.w3.org/1999/xhtml"{}>{}</div>"#,
|
||||
self.atts.iter().map(att_to_string).collect::<String>(),
|
||||
self.child
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, ElementType, Shape, SvgElement, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn ellipse() -> Ellipse {
|
||||
Ellipse(vec![])
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Ellipse(Vec<Att>);
|
||||
|
||||
impl Ellipse {
|
||||
pub fn id<V: Into<Value>>(&mut self, id: V) {
|
||||
self.0.push(att("id", id));
|
||||
}
|
||||
pub fn cx<V: Into<Value>>(mut self, cx: V) -> Self {
|
||||
self.0.push(att("cx", cx));
|
||||
self
|
||||
}
|
||||
pub fn cy<V: Into<Value>>(mut self, cy: V) -> Self {
|
||||
self.0.push(att("cy", cy));
|
||||
self
|
||||
}
|
||||
pub fn rx<V: Into<Value>>(mut self, rx: V) -> Self {
|
||||
self.0.push(att("rx", rx));
|
||||
self
|
||||
}
|
||||
pub fn ry<V: Into<Value>>(mut self, ry: V) -> Self {
|
||||
self.0.push(att("ry", ry));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape for Ellipse {
|
||||
fn fill<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.0.push(att("fill", value));
|
||||
self
|
||||
}
|
||||
|
||||
fn stroke<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.0.push(att("stroke", value));
|
||||
self
|
||||
}
|
||||
|
||||
fn transform<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.0.push(att("transform", value));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ellipse() {
|
||||
let ellipse = ellipse().cx(0).cy(0).rx(10).ry(15);
|
||||
assert_eq!(
|
||||
r#"<ellipse cx="0" cy="0" rx="10" ry="15" />"#,
|
||||
ellipse.to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
use crate::render::svglib::div::Div;
|
||||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn foreign_object() -> ForeignObject {
|
||||
ForeignObject::default()
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct ForeignObject {
|
||||
child: Option<Div>, //for now
|
||||
atts: Vec<Att>,
|
||||
}
|
||||
|
||||
impl ForeignObject {
|
||||
pub fn id<V>(&mut self, id: V)
|
||||
where
|
||||
V: Into<Value>,
|
||||
{
|
||||
self.atts.push(att("id", id));
|
||||
}
|
||||
|
||||
pub fn x<V: Into<Value>>(mut self, x: V) -> Self {
|
||||
self.atts.push(att("x", x));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn y<V: Into<Value>>(mut self, y: V) -> Self {
|
||||
self.atts.push(att("y", y));
|
||||
self
|
||||
}
|
||||
pub fn width<V: Into<Value>>(mut self, width: V) -> Self {
|
||||
self.atts.push(att("width", width));
|
||||
self
|
||||
}
|
||||
pub fn height<V: Into<Value>>(mut self, height: V) -> Self {
|
||||
self.atts.push(att("height", height));
|
||||
self
|
||||
}
|
||||
pub fn class<V: Into<Value>>(mut self, class: V) -> Self {
|
||||
self.atts.push(att("class", class));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_child(mut self, child: Div) -> Self {
|
||||
self.child = Some(child);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn group() -> Group {
|
||||
Group::default()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Group {
|
||||
children: Vec<Box<dyn SvgElement>>,
|
||||
atts: Vec<Att>,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub fn add(&mut self, child: impl SvgElement + 'static) {
|
||||
self.children.push(Box::new(child));
|
||||
}
|
||||
|
||||
pub fn id<V: Into<Value>>(&mut self, id: V) {
|
||||
self.atts.push(att("id", id));
|
||||
}
|
||||
|
||||
pub fn transform<V: Into<Value>>(&mut self, value: V) {
|
||||
self.atts.push(att("transform", value));
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
}
|
||||
|
||||
#[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 id="testgroup"><rect x="0" y="0" width="10" height="10" /></g>"#,
|
||||
g.to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn image() -> Image {
|
||||
Image::default()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Image {
|
||||
atts: Vec<Att>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn id<V: Into<Value>>(&mut self, id: V) {
|
||||
self.atts.push(att("id", id));
|
||||
}
|
||||
|
||||
pub fn transform<V: Into<Value>>(&mut self, value: V) {
|
||||
self.atts.push(att("transform", value));
|
||||
}
|
||||
pub fn x<V: Into<Value>>(mut self, x: V) -> Self {
|
||||
self.atts.push(att("x", x));
|
||||
self
|
||||
}
|
||||
pub fn y<V: Into<Value>>(mut self, y: V) -> Self {
|
||||
self.atts.push(att("y", y));
|
||||
self
|
||||
}
|
||||
pub fn width<V: Into<Value>>(mut self, width: V) -> Self {
|
||||
self.atts.push(att("width", width));
|
||||
self
|
||||
}
|
||||
pub fn height<V: Into<Value>>(mut self, height: V) -> Self {
|
||||
self.atts.push(att("height", height));
|
||||
self
|
||||
}
|
||||
pub fn href<V: Into<Value>>(mut self, href: V) -> Self {
|
||||
self.atts.push(att("href", href));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[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#"<image href="http://acme.com/coyote.jpg" x="0" y="0" width="10" height="10" />"#,
|
||||
rect.to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, SvgElement, ElementType, Shape, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn line() -> Line {
|
||||
Line(vec![])
|
||||
}
|
||||
|
||||
pub struct Line(Vec<Att>);
|
||||
|
||||
impl Line {
|
||||
pub fn id<V: Into<Value>>(mut self, id: V) -> Self {
|
||||
self.0.push(att("id", id));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn x1<V: Into<Value>>(mut self, x: V) -> Self {
|
||||
self.0.push(att("x1", x));
|
||||
self
|
||||
}
|
||||
pub fn y1<V: Into<Value>>(mut self, y: V) -> Self {
|
||||
self.0.push(att("y1", y));
|
||||
self
|
||||
}
|
||||
pub fn x2<V: Into<Value>>(mut self, x2: V) -> Self {
|
||||
self.0.push(att("x2", x2));
|
||||
self
|
||||
}
|
||||
pub fn y2<V: Into<Value>>(mut self, y2: V) -> Self {
|
||||
self.0.push(att("y2", y2));
|
||||
self
|
||||
}
|
||||
pub fn attr<V: Into<Value>>(mut self, key: &str, value: V) -> Self {
|
||||
self.0.push(att(key, value));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape for Line {
|
||||
fn fill<V>(mut self, value: V) -> Self
|
||||
where
|
||||
V: Into<Value>,
|
||||
{
|
||||
self.0.push(att("fill", value));
|
||||
self
|
||||
}
|
||||
|
||||
fn stroke<V>(mut self, value: V) -> Self
|
||||
where
|
||||
V: Into<Value>,
|
||||
{
|
||||
self.0.push(att("stroke", value));
|
||||
self
|
||||
}
|
||||
|
||||
fn transform<V>(mut self, value: V) -> Self
|
||||
where
|
||||
V: Into<Value>,
|
||||
{
|
||||
self.0.push(att("transform", value));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
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)])
|
||||
}
|
||||
|
||||
pub struct Link(Vec<Att>);
|
||||
|
||||
impl Link {
|
||||
pub fn id<V: Into<Value>>(&mut self, id: V) {
|
||||
self.0.push(att("id", id));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
pub mod circle;
|
||||
pub mod div;
|
||||
pub mod ellipse;
|
||||
pub mod foreign_object;
|
||||
pub mod group;
|
||||
pub mod image;
|
||||
pub mod line;
|
||||
pub mod link;
|
||||
pub mod path;
|
||||
pub mod rect;
|
||||
pub mod svg;
|
||||
pub mod text;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Value(String);
|
||||
|
||||
impl From<&str> for Value {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Value {
|
||||
fn from(s: usize) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Value {
|
||||
fn from(s: u32) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Value {
|
||||
fn from(s: i32) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Value {
|
||||
fn from(s: f32) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Value {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for Value {
|
||||
fn from(s: &String) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ElementType {
|
||||
Circle,
|
||||
Ellipse,
|
||||
ForeignObject,
|
||||
Group(Vec<ElementType>),
|
||||
Image,
|
||||
Line,
|
||||
Link,
|
||||
Path,
|
||||
Rect,
|
||||
}
|
||||
|
||||
pub trait SvgElement: Display {
|
||||
fn get_type(&self) -> ElementType;
|
||||
fn atts(&self) -> &[Att];
|
||||
}
|
||||
|
||||
pub trait Shape {
|
||||
fn fill<V>(self, value: V) -> Self
|
||||
where
|
||||
V: Into<Value>;
|
||||
|
||||
fn stroke<V>(self, value: V) -> Self
|
||||
where
|
||||
V: Into<Value>;
|
||||
|
||||
fn transform<V>(self, value: V) -> Self
|
||||
where
|
||||
V: Into<Value>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Att {
|
||||
name: String,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
impl Att {
|
||||
pub fn new(name: &str, value: Value) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn att<V>(name: &str, value: V) -> Att
|
||||
where
|
||||
V: Into<Value>,
|
||||
{
|
||||
Att::new(name, value.into())
|
||||
}
|
||||
|
||||
fn att_to_string(att: &Att) -> String {
|
||||
format!(r#" {}="{}""#, att.name, att.value)
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
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)
|
||||
}
|
||||
|
||||
pub struct Path {
|
||||
d: String,
|
||||
atts: Vec<Att>,
|
||||
}
|
||||
|
||||
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 atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
}
|
||||
|
||||
impl Path {
|
||||
pub fn new(d: &str) -> Self {
|
||||
Self {
|
||||
d: d.to_string(),
|
||||
atts: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id<V: Into<Value>>(&mut self, id: V) {
|
||||
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<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.atts.push(att("fill", value));
|
||||
self
|
||||
}
|
||||
|
||||
fn stroke<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.atts.push(att("stroke", value));
|
||||
self
|
||||
}
|
||||
|
||||
fn transform<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.atts.push(att("transform", value));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
|
||||
pub fn rect() -> Rect {
|
||||
Rect::default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Rect {
|
||||
atts: Vec<Att>,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub fn id<V: Into<Value>>(mut self, id: V) -> Self {
|
||||
self.atts.push(att("id", id));
|
||||
self
|
||||
}
|
||||
pub fn rounded() -> Self {
|
||||
Self { atts: vec![] }
|
||||
}
|
||||
|
||||
pub fn x<V: Into<Value>>(mut self, x: V) -> Self {
|
||||
self.atts.push(att("x", x));
|
||||
self
|
||||
}
|
||||
pub fn y<V: Into<Value>>(mut self, y: V) -> Self {
|
||||
self.atts.push(att("y", y));
|
||||
self
|
||||
}
|
||||
pub fn width<V: Into<Value>>(mut self, width: V) -> Self {
|
||||
self.atts.push(att("width", width));
|
||||
self
|
||||
}
|
||||
pub fn height<V: Into<Value>>(mut self, height: V) -> Self {
|
||||
self.atts.push(att("height", height));
|
||||
self
|
||||
}
|
||||
pub fn class<V: Into<Value>>(mut self, class: V) -> Self {
|
||||
self.atts.push(att("class", class));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fill<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.atts.push(att("fill", value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stroke<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.atts.push(att("stroke", value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn transform<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.atts.push(att("transform", value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn attr<V: Into<Value>>(mut self, key: &str, value: V) -> Self {
|
||||
self.atts.push(att(key, value));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
}
|
||||
|
||||
#[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 x="0" y="0" width="10" height="10" />"#,
|
||||
rect.to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, SvgElement, Value};
|
||||
use std::fmt;
|
||||
|
||||
pub fn svg() -> Svg {
|
||||
Svg::default()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Svg {
|
||||
style: Option<String>,
|
||||
elements: Vec<Box<dyn SvgElement>>,
|
||||
atts: Vec<Att>,
|
||||
}
|
||||
|
||||
impl Svg {
|
||||
pub fn style(&mut self, style: &str) {
|
||||
self.style = Some(style.to_string());
|
||||
}
|
||||
|
||||
pub fn add(&mut self, child: impl SvgElement + 'static) {
|
||||
self.elements.push(Box::new(child));
|
||||
}
|
||||
|
||||
pub fn width<V: Into<Value>>(&mut self, width: V) {
|
||||
self.atts.push(att("width", width.into().to_string()));
|
||||
}
|
||||
|
||||
pub fn height<V: Into<Value>>(&mut self, height: V) {
|
||||
self.atts.push(att("height", height.into().to_string()));
|
||||
}
|
||||
|
||||
pub fn viewbox<V: Into<Value>>(&mut self, viewbox: V) {
|
||||
self.atts.push(att("viewBox", viewbox));
|
||||
}
|
||||
|
||||
pub fn preserveaspectratio<V: Into<Value>>(&mut self, preserveaspectratio: V) {
|
||||
self.atts
|
||||
.push(att("preserveAspectRatio", preserveaspectratio));
|
||||
}
|
||||
|
||||
pub fn transform<V: Into<Value>>(&mut self, transform: V) {
|
||||
self.atts.push(att("transform", transform));
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Svg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg"{}>{}"#,
|
||||
self.atts.iter().map(att_to_string).collect::<String>(),
|
||||
self.style
|
||||
.as_ref()
|
||||
.map(|s| format!("<style>{}</style>", s))
|
||||
.unwrap_or("".to_string())
|
||||
)?;
|
||||
|
||||
for e in &self.elements {
|
||||
write!(f, "{}", e.to_string().as_str())?;
|
||||
}
|
||||
write!(f, "</svg>")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::render::svglib::rect::rect;
|
||||
|
||||
#[test]
|
||||
fn style() {
|
||||
let mut svg = svg();
|
||||
svg.style(".id { background-color: red; }");
|
||||
assert_eq!(
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg"><style>.id { background-color: red; }</style></svg>"#,
|
||||
svg.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_rect() {
|
||||
let mut svg = svg();
|
||||
svg.preserveaspectratio("none");
|
||||
svg.add(rect().x(0).y(0).width(10).height(10));
|
||||
assert_eq!(
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none"><rect x="0" y="0" width="10" height="10" /></svg>"#,
|
||||
svg.to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
pub fn text() -> Text {
|
||||
Text::default()
|
||||
}
|
||||
|
||||
pub struct Text {
|
||||
atts: Vec<Att>,
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Default for Text {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
atts: vec![],
|
||||
text: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Text {
|
||||
pub fn text<V: Into<String>>(mut self, text: V) -> Self {
|
||||
self.text = text.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rounded() -> Self {
|
||||
Self {
|
||||
atts: vec![],
|
||||
text: "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn x<V: Into<Value>>(mut self, x: V) -> Self {
|
||||
self.atts.push(att("x", x));
|
||||
self
|
||||
}
|
||||
pub fn y<V: Into<Value>>(mut self, y: V) -> Self {
|
||||
self.atts.push(att("y", y));
|
||||
self
|
||||
}
|
||||
pub fn width<V: Into<Value>>(mut self, width: V) -> Self {
|
||||
self.atts.push(att("width", width));
|
||||
self
|
||||
}
|
||||
pub fn height<V: Into<Value>>(mut self, height: V) -> Self {
|
||||
self.atts.push(att("height", height));
|
||||
self
|
||||
}
|
||||
pub fn class<V: Into<Value>>(mut self, class: V) -> Self {
|
||||
self.atts.push(att("class", class));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fill<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.atts.push(att("fill", value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stroke<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.atts.push(att("stroke", value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn transform<V: Into<Value>>(mut self, value: V) -> Self {
|
||||
self.atts.push(att("transform", value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn attr<V: Into<Value>>(mut self, key: &str, value: V) -> Self {
|
||||
self.atts.push(att(key, value));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Text {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<text{}>{}</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();
|
||||
let x: String = x[0].value.to_string();
|
||||
let _ = write!(output, "<tspan x=\"{}\" dy=\"10\">{}</tspan>", x, l);
|
||||
output
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Text {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Rect
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_rect() {
|
||||
let text = text().x(0).y(0).width(10).height(10);
|
||||
assert_eq!(
|
||||
r#"<text x="0" y="0" width="10" height="10"></text>"#,
|
||||
text.to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,11 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::parse::tokens::{
|
||||
use crate::tokens::{
|
||||
Token,
|
||||
TokenType::{self, *},
|
||||
KEYWORDS,
|
||||
};
|
||||
|
||||
static KEYWORDS: Lazy<HashMap<&'static str, TokenType>> = Lazy::new(|| {
|
||||
HashMap::from([
|
||||
("structure", Structure),
|
||||
("styles", Styles),
|
||||
("group", Group),
|
||||
("px", Px),
|
||||
])
|
||||
});
|
||||
|
||||
pub fn scan(vis: &str) -> anyhow::Result<Vec<Token>> {
|
||||
let mut scanner = Scanner::new(vis);
|
||||
scanner.scan();
|
||||
|
|
@ -58,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("<") {
|
||||
|
|
@ -132,11 +122,14 @@ impl<'a> Scanner<'a> {
|
|||
}
|
||||
|
||||
fn identifier(&mut self) {
|
||||
while is_alpha(self.peek()) || is_digit(self.peek()) || self.peek() == "-" {
|
||||
while is_alpha(self.peek()) || is_digit(self.peek()) {
|
||||
self.advance();
|
||||
}
|
||||
let text = self.chars[self.start_pos..self.current_pos].concat();
|
||||
let tokentype = KEYWORDS.get(text.as_str()).cloned().unwrap_or(Identifier);
|
||||
let tokentype = KEYWORDS
|
||||
.get(text.as_str())
|
||||
.map(|d| *d)
|
||||
.unwrap_or(Identifier);
|
||||
|
||||
self.add_token(tokentype);
|
||||
}
|
||||
|
|
@ -182,7 +175,7 @@ impl<'a> Scanner<'a> {
|
|||
return false;
|
||||
}
|
||||
self.current_pos += 1;
|
||||
true
|
||||
return true;
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> &str {
|
||||
|
|
@ -199,14 +192,18 @@ impl<'a> Scanner<'a> {
|
|||
}
|
||||
|
||||
fn is_digit(char: &str) -> bool {
|
||||
!char.is_empty() && char.chars().next().unwrap().is_ascii_digit()
|
||||
char.len() > 0 && char.chars().next().unwrap().is_ascii_digit()
|
||||
}
|
||||
|
||||
fn is_alpha(char: &str) -> bool {
|
||||
if char.is_empty() {
|
||||
if char.len() == 0 {
|
||||
false
|
||||
} else {
|
||||
let char = char.chars().next().unwrap();
|
||||
char.is_alphabetic() || char == '_'
|
||||
if char.is_alphabetic() || char == '_' {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,16 @@
|
|||
use std::cell::LazyCell;
|
||||
use std::collections::HashMap;
|
||||
use TokenType::*;
|
||||
|
||||
pub const KEYWORDS: LazyCell<HashMap<&str, TokenType>> = LazyCell::new(|| {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("structure", Structure);
|
||||
m.insert("styles", Styles);
|
||||
m.insert("group", Group);
|
||||
m.insert("px", Px);
|
||||
m
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Token {
|
||||
pub tokentype: TokenType,
|
||||
Loading…
Add table
Reference in a new issue