1use std::io::Write;
2
3use crate::escape::escape_xml;
4pub use circle::*;
5pub use color::*;
6use common_attributes::*;
7pub use element::*;
8pub use group::*;
9pub use id::*;
10pub use path::*;
11pub use rect::*;
12pub use script::*;
13pub use stroke::*;
14pub use style::*;
15pub use svg_use::*;
16pub use transform::*;
17pub use view_box::*;
18
19pub mod circle;
20pub mod color;
21pub mod common_attributes;
22pub mod element;
23pub(crate) mod escape;
24pub mod group;
25pub mod id;
26pub mod path;
27pub mod rect;
28pub mod script;
29pub mod stroke;
30pub mod style;
31pub mod svg_use;
32pub mod transform;
33pub mod view_box;
34
35pub type Coordinate = f64;
36
37pub type SvgInteger = i64;
38
39enum OptionalSvgId {
40 None,
41 Some(SvgId),
42 Def(SvgId),
43}
44
45pub struct SvgImage {
46 id_sequence: u32,
47 dimensions: Option<(SvgInteger, SvgInteger)>,
48 view_box: Option<ViewBox>,
49 elements: Vec<(OptionalSvgId, SvgElement)>,
50 data_attributes: Vec<(String, String)>,
51 common_attributes: CommonAttributes,
52}
53
54implement_common_attributes!(SvgImage);
55
56impl SvgImage {
57 pub const fn new() -> Self {
58 Self {
59 id_sequence: 0,
60 dimensions: None,
61 view_box: None,
62 elements: Vec::new(),
63 data_attributes: Vec::new(),
64 common_attributes: CommonAttributes::new(),
65 }
66 }
67
68 pub const fn dimensions(mut self, width: SvgInteger, height: SvgInteger) -> Self {
69 self.dimensions = Some((width, height));
70 self
71 }
72
73 pub fn data_attribute(mut self, name: String, value: String) -> Self {
74 self.data_attributes.push((name, value));
75 self
76 }
77
78 pub fn view_box<V: Into<ViewBox>>(mut self, view_box: V) -> Self {
79 self.view_box = Some(view_box.into());
80 self
81 }
82
83 pub fn add<E: Into<SvgElement>>(&mut self, element: E) -> &mut Self {
84 self.elements.push((OptionalSvgId::None, element.into()));
85 self
86 }
87
88 pub fn add_with_id<E: Into<SvgElement>>(&mut self, element: E) -> SvgId {
89 let new_id = SvgId {
90 value: self.id_sequence,
91 };
92 self.id_sequence += 1;
93 self.elements
94 .push((OptionalSvgId::Some(new_id), element.into()));
95 new_id
96 }
97
98 pub fn define<E: Into<SvgElement>>(&mut self, element: E) -> SvgId {
99 let new_id = SvgId {
100 value: self.id_sequence,
101 };
102 self.id_sequence += 1;
103 self.elements
104 .push((OptionalSvgId::Def(new_id), element.into()));
105 new_id
106 }
107
108 pub fn to_svg_string(&self) -> String {
109 #![allow(clippy::unwrap_used)]
110 let mut buffer = Vec::new();
111 buffer
112 .write_all(b"<svg xmlns=\"http://www.w3.org/2000/svg\"")
113 .unwrap();
114 if let Some((x, y)) = &self.dimensions {
115 buffer
116 .write_all(format!(" x=\"{x}\" y=\"{y}\"").as_bytes())
117 .unwrap();
118 }
119 if let Some(view_box) = &self.view_box {
120 let s = format!(
121 " viewBox=\"{} {} {} {}\" preserveAspectRatio=\"xMidYMid\"",
122 view_box.min_x, view_box.min_y, view_box.width, view_box.height
123 );
124 buffer.write_all(s.as_bytes()).unwrap();
125 }
126 for (name, value) in &self.data_attributes {
127 buffer
128 .write_all(
129 format!("data-{}=\"{}\"", escape_xml(name), escape_xml(value)).as_bytes(),
130 )
131 .unwrap();
132 }
133 self.common_attributes.write(&mut buffer);
134 buffer.write_all(b">\n").unwrap();
135
136 let mut first = true;
137 for (id, element) in &self.elements {
138 if let OptionalSvgId::Def(id) = id {
139 if first {
140 first = false;
141 buffer.write_all(b"<defs>").unwrap();
142 }
143 element.write(Some(*id), &mut buffer);
144 }
145 }
146 if !first {
147 buffer.write_all(b"</defs>").unwrap();
148 }
149
150 for (id, element) in &self.elements {
151 match id {
152 OptionalSvgId::None => {
153 element.write(None, &mut buffer);
154 }
155 OptionalSvgId::Some(id) => {
156 element.write(Some(*id), &mut buffer);
157 }
158 OptionalSvgId::Def(_) => {}
159 }
160 }
161
162 buffer.write_all(b"</svg>").unwrap();
163 String::from_utf8(buffer).unwrap()
164 }
165}
166
167#[test]
168fn test() {
169 let mut image = SvgImage::new()
170 .dimensions(200, 00)
171 .view_box((-100, -100, 200, 200))
172 .style("--step: 0");
173 for (offset_x, offset_y, color) in [
174 (-100, -100, (0xFF, 0, 0)),
175 (0, -100, (0, 0xFF, 0)),
176 (-100, 0, (0, 0, 0xFF)),
177 (0, 0, (0xFF, 0xFF, 0xFF)),
178 ] {
179 let id = image.add_with_id(
180 SvgGroup::with_elements(vec![SvgRect::default()
181 .x(offset_x)
182 .y(offset_y)
183 .width(100)
184 .height(100)
185 .fill(SvgColor::Rgb(color.0, color.1, color.2))])
186 .style("opacity: 0"),
187 );
188 if color.0 == 0xff {
189 image.add(SvgScript::new(format!(
190 "setTimeout(() => {{ document.getElementById('{id}').remove(); }}, 1000);"
191 )));
192 }
193 }
194 image.add(SvgPath {
195 stroke: Some(SvgColor::Rgb(0xFF, 0xFF, 0)),
196 shape: SvgShape::at(10.6, 10.).close(),
197 ..Default::default()
198 });
199}