1use alloc::{string::String, vec::Vec};
4use core::fmt::{self, Write};
5use types::BoundingBox;
6
7pub trait OutlinePen {
9 fn move_to(&mut self, x: f32, y: f32);
11
12 fn line_to(&mut self, x: f32, y: f32);
14
15 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32);
18
19 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32);
22
23 fn close(&mut self);
25}
26
27#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
29pub enum PathElement {
30 MoveTo { x: f32, y: f32 },
32 LineTo { x: f32, y: f32 },
34 QuadTo { cx0: f32, cy0: f32, x: f32, y: f32 },
37 CurveTo {
40 cx0: f32,
41 cy0: f32,
42 cx1: f32,
43 cy1: f32,
44 x: f32,
45 y: f32,
46 },
47 Close,
49}
50
51impl OutlinePen for Vec<PathElement> {
52 fn move_to(&mut self, x: f32, y: f32) {
53 self.push(PathElement::MoveTo { x, y })
54 }
55
56 fn line_to(&mut self, x: f32, y: f32) {
57 self.push(PathElement::LineTo { x, y })
58 }
59
60 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
61 self.push(PathElement::QuadTo { cx0, cy0, x, y })
62 }
63
64 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
65 self.push(PathElement::CurveTo {
66 cx0,
67 cy0,
68 cx1,
69 cy1,
70 x,
71 y,
72 })
73 }
74
75 fn close(&mut self) {
76 self.push(PathElement::Close)
77 }
78}
79
80pub struct NullPen;
82
83impl OutlinePen for NullPen {
84 fn move_to(&mut self, _x: f32, _y: f32) {}
85 fn line_to(&mut self, _x: f32, _y: f32) {}
86 fn quad_to(&mut self, _cx0: f32, _cy0: f32, _x: f32, _y: f32) {}
87 fn curve_to(&mut self, _cx0: f32, _cy0: f32, _cx1: f32, _cy1: f32, _x: f32, _y: f32) {}
88 fn close(&mut self) {}
89}
90
91#[derive(Clone, Default, Debug)]
93pub struct SvgPen(String, Option<usize>);
94
95impl SvgPen {
96 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn with_precision(precision: usize) -> Self {
105 Self(String::default(), Some(precision))
106 }
107
108 pub fn clear(&mut self) {
110 self.0.clear();
111 }
112
113 fn maybe_push_space(&mut self) {
114 if !self.0.is_empty() {
115 self.0.push(' ');
116 }
117 }
118}
119
120impl core::ops::Deref for SvgPen {
121 type Target = str;
122
123 fn deref(&self) -> &Self::Target {
124 self.0.as_str()
125 }
126}
127
128impl OutlinePen for SvgPen {
129 fn move_to(&mut self, x: f32, y: f32) {
130 self.maybe_push_space();
131 let _ = if let Some(prec) = self.1 {
132 write!(self.0, "M{x:.0$},{y:.0$}", prec)
133 } else {
134 write!(self.0, "M{x},{y}")
135 };
136 }
137
138 fn line_to(&mut self, x: f32, y: f32) {
139 self.maybe_push_space();
140 let _ = if let Some(prec) = self.1 {
141 write!(self.0, "L{x:.0$},{y:.0$}", prec)
142 } else {
143 write!(self.0, "L{x},{y}")
144 };
145 }
146
147 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
148 self.maybe_push_space();
149 let _ = if let Some(prec) = self.1 {
150 write!(self.0, "Q{cx0:.0$},{cy0:.0$} {x:.0$},{y:.0$}", prec)
151 } else {
152 write!(self.0, "Q{cx0},{cy0} {x},{y}")
153 };
154 }
155
156 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
157 self.maybe_push_space();
158 let _ = if let Some(prec) = self.1 {
159 write!(
160 self.0,
161 "C{cx0:.0$},{cy0:.0$} {cx1:.0$},{cy1:.0$} {x:.0$},{y:.0$}",
162 prec
163 )
164 } else {
165 write!(self.0, "C{cx0},{cy0} {cx1},{cy1} {x},{y}")
166 };
167 }
168
169 fn close(&mut self) {
170 self.maybe_push_space();
171 self.0.push('Z');
172 }
173}
174
175impl AsRef<str> for SvgPen {
176 fn as_ref(&self) -> &str {
177 self.0.as_ref()
178 }
179}
180
181impl From<String> for SvgPen {
182 fn from(value: String) -> Self {
183 Self(value, None)
184 }
185}
186
187impl From<SvgPen> for String {
188 fn from(value: SvgPen) -> Self {
189 value.0
190 }
191}
192
193impl fmt::Display for SvgPen {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 write!(f, "{}", self.0)
196 }
197}
198
199#[derive(Clone, Default, Debug)]
201pub struct ControlBoundsPen(Option<BoundingBox<f32>>);
202
203impl ControlBoundsPen {
204 pub fn new() -> Self {
206 Self(None)
207 }
208
209 pub fn bounding_box(&self) -> Option<BoundingBox<f32>> {
211 self.0
212 }
213
214 fn update_bounds(&mut self, x: f32, y: f32) {
215 if let Some(bb) = &mut self.0 {
216 bb.x_min = bb.x_min.min(x);
217 bb.y_min = bb.y_min.min(y);
218 bb.x_max = bb.x_max.max(x);
219 bb.y_max = bb.y_max.max(y);
220 } else {
221 self.0 = Some(BoundingBox {
222 x_min: x,
223 y_min: y,
224 x_max: x,
225 y_max: y,
226 });
227 }
228 }
229}
230
231impl OutlinePen for ControlBoundsPen {
232 fn move_to(&mut self, x: f32, y: f32) {
233 self.update_bounds(x, y);
234 }
235
236 fn line_to(&mut self, x: f32, y: f32) {
237 self.update_bounds(x, y);
238 }
239
240 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
241 self.update_bounds(cx0, cy0);
242 self.update_bounds(x, y);
243 }
244
245 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
246 self.update_bounds(cx0, cy0);
247 self.update_bounds(cx1, cy1);
248 self.update_bounds(x, y);
249 }
250
251 fn close(&mut self) {}
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn svg_pen_precision() {
260 let svg_data = [None, Some(1), Some(4)].map(|prec| {
261 let mut pen = match prec {
262 None => SvgPen::new(),
263 Some(prec) => SvgPen::with_precision(prec),
264 };
265 pen.move_to(1.0, 2.45556);
266 pen.line_to(1.2, 4.0);
267 pen.quad_to(2.0345, 3.56789, -0.157, -425.07);
268 pen.curve_to(-37.0010, 4.5, 2.0, 1.0, -0.5, -0.25);
269 pen.close();
270 pen.to_string()
271 });
272 let expected = [
273 "M1,2.45556 L1.2,4 Q2.0345,3.56789 -0.157,-425.07 C-37.001,4.5 2,1 -0.5,-0.25 Z",
274 "M1.0,2.5 L1.2,4.0 Q2.0,3.6 -0.2,-425.1 C-37.0,4.5 2.0,1.0 -0.5,-0.2 Z",
275 "M1.0000,2.4556 L1.2000,4.0000 Q2.0345,3.5679 -0.1570,-425.0700 C-37.0010,4.5000 2.0000,1.0000 -0.5000,-0.2500 Z"
276 ];
277 for (result, expected) in svg_data.iter().zip(&expected) {
278 assert_eq!(result, expected);
279 }
280 }
281}