use std::fmt;
use std::slice::Iter;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ElementKind {
A, Defs, FeDropShadow, Filter, Group, Line, Path, Rect, Style, Svg, Symbol, Text, Title, Use, }
impl ElementKind {
pub fn name(&self) -> &'static str {
match self {
ElementKind::A => "a",
ElementKind::Defs => "defs",
ElementKind::Filter => "filter",
ElementKind::FeDropShadow => "feDropShadow",
ElementKind::Group => "g",
ElementKind::Line => "line",
ElementKind::Path => "path",
ElementKind::Rect => "rect",
ElementKind::Style => "style",
ElementKind::Svg => "svg",
ElementKind::Symbol => "symbol",
ElementKind::Text => "text",
ElementKind::Title => "title",
ElementKind::Use => "use",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Element {
kind: ElementKind,
attrs: Vec<Attribute>,
children: Vec<Element>,
content: Option<String>,
}
impl Element {
pub fn new(kind: ElementKind) -> Element {
Element {
kind,
attrs: vec![],
children: vec![],
content: None,
}
}
pub fn add_attr(&mut self, attr: Attribute) {
self.attrs.push(attr);
}
pub fn attrs(&self) -> Iter<'_, Attribute> {
self.attrs.iter()
}
pub fn add_child(&mut self, child: Element) {
self.children.push(child);
}
pub fn children(&self) -> Iter<'_, Element> {
self.children.iter()
}
pub fn has_children(&self) -> bool {
!self.children.is_empty()
}
pub fn kind(&self) -> ElementKind {
self.kind
}
pub fn set_content(&mut self, content: &str) {
self.content = Some(content.to_string());
}
pub fn has_content(&self) -> bool {
self.content.is_some()
}
pub fn content(&self) -> &str {
match &self.content {
None => "",
Some(e) => e,
}
}
fn to_svg(&self, buffer: &mut String) {
let name = self.kind().name();
buffer.push('<');
buffer.push_str(name);
if self.kind() == ElementKind::Svg {
push_attr(buffer, "xmlns", "http://www.w3.org/2000/svg");
}
for att in self.attrs() {
buffer.push(' ');
buffer.push_str(&att.to_string());
}
if self.has_children() || self.has_content() {
buffer.push('>');
for child in self.children() {
child.to_svg(buffer);
}
buffer.push_str(self.content());
buffer.push_str("</");
buffer.push_str(name);
buffer.push('>');
} else {
buffer.push_str(" />");
}
}
}
impl fmt::Display for Element {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut text = String::new();
self.to_svg(&mut text);
f.write_str(&text)
}
}
fn push_attr(f: &mut String, key: &str, value: &str) {
f.push_str(&format!(" {key}=\"{value}\""));
}
#[derive(Clone, Debug, PartialEq)]
pub enum Attribute {
Class(String), D(String), DX(f64), DY(f64), Fill(String), Filter(String), FloodOpacity(f64), FontFamily(String), FontSize(String), FontWeight(String), Height(String), Href(String), Id(String), Opacity(f64), StdDeviation(f64), Stroke(String), StrokeWidth(f64), TextDecoration(String), ViewBox(f64, f64, f64, f64), Width(String), X(f64), X1(f64), X2(f64), Y(f64), Y1(f64), Y2(f64), }
impl Attribute {
fn name(&self) -> &'static str {
match self {
Attribute::Class(_) => "class",
Attribute::D(_) => "d",
Attribute::DX(_) => "dx",
Attribute::DY(_) => "dy",
Attribute::Fill(_) => "fill",
Attribute::Filter(_) => "filter",
Attribute::FloodOpacity(_) => "flood-opacity",
Attribute::FontFamily(_) => "font-family",
Attribute::FontSize(_) => "font-size",
Attribute::FontWeight(_) => "font-weight",
Attribute::Height(_) => "height",
Attribute::Href(_) => "href",
Attribute::Id(_) => "id",
Attribute::Opacity(_) => "opacity",
Attribute::StdDeviation(_) => "stdDeviation",
Attribute::Stroke(_) => "stroke",
Attribute::StrokeWidth(_) => "stroke-width",
Attribute::TextDecoration(_) => "text-decoration",
Attribute::ViewBox(_, _, _, _) => "viewBox",
Attribute::Width(_) => "width",
Attribute::X(_) => "x",
Attribute::X1(_) => "x1",
Attribute::X2(_) => "x2",
Attribute::Y(_) => "y",
Attribute::Y1(_) => "y1",
Attribute::Y2(_) => "y2",
}
}
}
impl fmt::Display for Attribute {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value = match self {
Attribute::Class(value) => value.clone(),
Attribute::D(value) => value.clone(),
Attribute::DX(value) => value.to_string(),
Attribute::DY(value) => value.to_string(),
Attribute::Fill(value) => value.clone(),
Attribute::Filter(value) => value.clone(),
Attribute::FloodOpacity(value) => value.to_string(),
Attribute::FontFamily(value) => value.clone(),
Attribute::FontSize(value) => value.clone(),
Attribute::FontWeight(value) => value.clone(),
Attribute::Height(value) => value.to_string(),
Attribute::Href(value) => value.to_string(),
Attribute::Id(value) => value.clone(),
Attribute::Opacity(value) => value.to_string(),
Attribute::StdDeviation(value) => value.to_string(),
Attribute::Stroke(value) => value.to_string(),
Attribute::StrokeWidth(value) => value.to_string(),
Attribute::TextDecoration(value) => value.clone(),
Attribute::ViewBox(min_x, min_y, width, height) => {
format!("{min_x} {min_y} {width} {height}")
}
Attribute::Width(value) => value.to_string(),
Attribute::X(value) => value.to_string(),
Attribute::X1(value) => value.to_string(),
Attribute::X2(value) => value.to_string(),
Attribute::Y(value) => value.to_string(),
Attribute::Y1(value) => value.to_string(),
Attribute::Y2(value) => value.to_string(),
};
f.write_str(&format!("{}=\"{}\"", self.name(), value))
}
}
pub fn new_a(href: &str) -> Element {
let mut elt = Element::new(ElementKind::A);
elt.add_attr(Attribute::Href(href.to_string()));
elt
}
pub fn new_svg() -> Element {
Element::new(ElementKind::Svg)
}
pub fn new_group() -> Element {
Element::new(ElementKind::Group)
}
pub fn new_style(content: &str) -> Element {
let mut elt = Element::new(ElementKind::Style);
elt.set_content(content);
elt
}
pub fn new_text(x: f64, y: f64, content: &str) -> Element {
let mut elt = Element::new(ElementKind::Text);
elt.add_attr(Attribute::X(x));
elt.add_attr(Attribute::Y(y));
elt.set_content(content);
elt
}
pub fn new_line(x1: f64, y1: f64, x2: f64, y2: f64) -> Element {
let mut elt = Element::new(ElementKind::Line);
elt.add_attr(Attribute::X1(x1));
elt.add_attr(Attribute::Y1(y1));
elt.add_attr(Attribute::X2(x2));
elt.add_attr(Attribute::Y2(y2));
elt
}
pub fn new_rect(x: f64, y: f64, width: f64, height: f64, fill: &str) -> Element {
let mut elt = Element::new(ElementKind::Rect);
elt.add_attr(Attribute::X(x));
elt.add_attr(Attribute::Y(y));
elt.add_attr(Attribute::Width(width.to_string()));
elt.add_attr(Attribute::Height(height.to_string()));
elt.add_attr(Attribute::Fill(fill.to_string()));
elt
}
pub fn new_defs() -> Element {
Element::new(ElementKind::Defs)
}
pub fn new_filter() -> Element {
Element::new(ElementKind::Filter)
}
pub fn new_fe_drop_shadow() -> Element {
Element::new(ElementKind::FeDropShadow)
}
pub fn new_symbol() -> Element {
Element::new(ElementKind::Symbol)
}
pub fn new_path(d: &str) -> Element {
let mut elt = Element::new(ElementKind::Path);
elt.add_attr(Attribute::D(d.to_string()));
elt
}
pub fn new_use() -> Element {
Element::new(ElementKind::Use)
}
pub fn new_title(content: &str) -> Element {
let mut elt = Element::new(ElementKind::Title);
elt.set_content(content);
elt
}
#[cfg(test)]
mod tests {
use super::Attribute::*;
use super::*;
#[test]
fn simple_line_svg() {
let mut elt = new_line(0.0, 80.0, 100.0, 20.0);
elt.add_attr(Stroke("black".to_string()));
assert_eq!(
elt.to_string(),
r#"<line x1="0" y1="80" x2="100" y2="20" stroke="black" />"#
);
}
#[test]
fn group_svg() {
let mut root = new_svg();
root.add_attr(ViewBox(0.0, 0.0, 100.0, 100.0));
let mut group = new_group();
group.add_attr(Fill("white".to_string()));
group.add_attr(Stroke("green".to_string()));
group.add_attr(StrokeWidth(5.0));
let elt = new_rect(0.0, 0.0, 40.0, 60.0, "#fff");
group.add_child(elt);
let elt = new_rect(20.0, 10.0, 3.5, 15.0, "red");
group.add_child(elt);
root.add_child(group);
assert_eq!(
root.to_string(),
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">\
<g fill=\"white\" stroke=\"green\" stroke-width=\"5\">\
<rect x=\"0\" y=\"0\" width=\"40\" height=\"60\" fill=\"#fff\" />\
<rect x=\"20\" y=\"10\" width=\"3.5\" height=\"15\" fill=\"red\" />\
</g>\
</svg>"
);
}
}