svgplot 2022.0.46

Utilities for SVG generation
Documentation
use std::io::Write;

use crate::escape::escape_xml;
pub use circle::*;
pub use color::*;
use common_attributes::*;
pub use element::*;
pub use group::*;
pub use id::*;
pub use path::*;
pub use rect::*;
pub use script::*;
pub use stroke::*;
pub use style::*;
pub use svg_use::*;
pub use symbol::*;
pub use transform::*;
pub use view_box::*;

pub mod circle;
pub mod color;
pub mod common_attributes;
pub mod element;
pub(crate) mod escape;
pub mod group;
pub mod id;
pub mod path;
pub mod rect;
pub mod script;
pub mod stroke;
pub mod style;
pub mod svg_use;
pub mod symbol;
pub mod transform;
pub mod view_box;

pub type Coordinate = f64;

pub type SvgInteger = i64;

enum OptionalSvgId {
    None,
    Some(SvgId),
    Def(SvgId),
}

pub struct SvgImage {
    id_sequence: u32,
    dimensions: Option<(SvgInteger, SvgInteger)>,
    view_box: Option<ViewBox>,
    elements: Vec<(OptionalSvgId, SvgElement)>,
    data_attributes: Vec<(String, String)>,
    common_attributes: CommonAttributes,
}

implement_common_attributes!(SvgImage);

impl SvgImage {
    pub const fn new() -> Self {
        Self {
            id_sequence: 0,
            dimensions: None,
            view_box: None,
            elements: Vec::new(),
            data_attributes: Vec::new(),
            common_attributes: CommonAttributes::new(),
        }
    }

    pub const fn dimensions(mut self, width: SvgInteger, height: SvgInteger) -> Self {
        self.dimensions = Some((width, height));
        self
    }

    pub fn data_attribute(mut self, name: String, value: String) -> Self {
        self.data_attributes.push((name, value));
        self
    }

    pub fn view_box<V: Into<ViewBox>>(mut self, view_box: V) -> Self {
        self.view_box = Some(view_box.into());
        self
    }

    pub fn add<E: Into<SvgElement>>(&mut self, element: E) -> &mut Self {
        self.elements.push((OptionalSvgId::None, element.into()));
        self
    }

    pub fn add_with_id<E: Into<SvgElement>>(&mut self, element: E) -> SvgId {
        let new_id = SvgId {
            value: self.id_sequence,
        };
        self.id_sequence += 1;
        self.elements
            .push((OptionalSvgId::Some(new_id), element.into()));
        new_id
    }

    pub fn define<E: Into<SvgElement>>(&mut self, element: E) -> SvgId {
        let new_id = SvgId {
            value: self.id_sequence,
        };
        self.id_sequence += 1;
        self.elements
            .push((OptionalSvgId::Def(new_id), element.into()));
        new_id
    }

    pub fn to_svg_string(&self) -> String {
        #![allow(clippy::unwrap_used)]
        let mut buffer = Vec::new();
        buffer
            .write_all(b"<svg xmlns=\"http://www.w3.org/2000/svg\"")
            .unwrap();
        if let Some((x, y)) = &self.dimensions {
            buffer
                .write_all(format!(" x=\"{x}\" y=\"{y}\"").as_bytes())
                .unwrap();
        }
        if let Some(view_box) = &self.view_box {
            let s = format!(
                " viewBox=\"{} {} {} {}\" preserveAspectRatio=\"xMidYMid\"",
                view_box.min_x, view_box.min_y, view_box.width, view_box.height
            );
            buffer.write_all(s.as_bytes()).unwrap();
        }
        for (name, value) in &self.data_attributes {
            buffer
                .write_all(
                    format!("data-{}=\"{}\"", escape_xml(name), escape_xml(value)).as_bytes(),
                )
                .unwrap();
        }
        self.common_attributes.write(&mut buffer);
        buffer.write_all(&[b'>', b'\n']).unwrap();

        let mut first = true;
        for (id, element) in &self.elements {
            if let OptionalSvgId::Def(id) = id {
                if first {
                    first = false;
                    buffer.write_all(b"<defs>").unwrap();
                }
                element.write(Some(*id), &mut buffer);
            }
        }
        if !first {
            buffer.write_all(b"</defs>").unwrap();
        }

        for (id, element) in &self.elements {
            match id {
                OptionalSvgId::None => {
                    element.write(None, &mut buffer);
                }
                OptionalSvgId::Some(id) => {
                    element.write(Some(*id), &mut buffer);
                }
                OptionalSvgId::Def(_) => {}
            }
        }

        buffer.write_all(b"</svg>").unwrap();
        String::from_utf8(buffer).unwrap()
    }
}

#[test]
fn test() {
    let mut image = SvgImage::new()
        .dimensions(200, 00)
        .view_box((-100, -100, 200, 200))
        .style("--step: 0");
    for (offset_x, offset_y, color) in [
        (-100, -100, (0xFF, 0, 0)),
        (0, -100, (0, 0xFF, 0)),
        (-100, 0, (0, 0, 0xFF)),
        (0, 0, (0xFF, 0xFF, 0xFF)),
    ] {
        let id = image.add_with_id(
            SvgGroup::with_elements(vec![SvgRect::default()
                .x(offset_x)
                .y(offset_y)
                .width(100)
                .height(100)
                .fill(SvgColor::Rgb(color.0, color.1, color.2))])
            .style("opacity: 0"),
        );
        if color.0 == 0xff {
            image.add(SvgScript::new(format!(
                "setTimeout(() => {{ document.getElementById('{id}').remove(); }}, 1000);"
            )));
        }
    }
    image.add(SvgPath {
        stroke: Some(SvgColor::Rgb(0xFF, 0xFF, 0)),
        shape: SvgShape::at(10.6, 10.).close(),
        ..Default::default()
    });
    println!("###:\n{}\n###", image.to_svg_string());
    /*

    */
}