1use alloc::{string::String, vec::Vec};
4use core::fmt::{self, Write};
5use types::{BoundingBox, Matrix};
6
7pub trait PathSink {
12 fn move_to(&mut self, x: f32, y: f32);
14
15 fn line_to(&mut self, x: f32, y: f32);
17
18 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32);
21
22 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32);
25
26 fn close(&mut self);
28}
29
30#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
32pub enum PathElement {
33 MoveTo { x: f32, y: f32 },
35 LineTo { x: f32, y: f32 },
37 QuadTo { cx0: f32, cy0: f32, x: f32, y: f32 },
40 CurveTo {
43 cx0: f32,
44 cy0: f32,
45 cx1: f32,
46 cy1: f32,
47 x: f32,
48 y: f32,
49 },
50 Close,
52}
53
54impl PathSink for Vec<PathElement> {
55 fn move_to(&mut self, x: f32, y: f32) {
56 self.push(PathElement::MoveTo { x, y })
57 }
58
59 fn line_to(&mut self, x: f32, y: f32) {
60 self.push(PathElement::LineTo { x, y })
61 }
62
63 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
64 self.push(PathElement::QuadTo { cx0, cy0, x, y })
65 }
66
67 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
68 self.push(PathElement::CurveTo {
69 cx0,
70 cy0,
71 cx1,
72 cy1,
73 x,
74 y,
75 })
76 }
77
78 fn close(&mut self) {
79 self.push(PathElement::Close)
80 }
81}
82
83pub struct NullSink;
85
86impl PathSink for NullSink {
87 fn move_to(&mut self, _x: f32, _y: f32) {}
88 fn line_to(&mut self, _x: f32, _y: f32) {}
89 fn quad_to(&mut self, _cx0: f32, _cy0: f32, _x: f32, _y: f32) {}
90 fn curve_to(&mut self, _cx0: f32, _cy0: f32, _cx1: f32, _cy1: f32, _x: f32, _y: f32) {}
91 fn close(&mut self) {}
92}
93
94#[derive(Clone, Default, Debug)]
96pub struct SvgSink(String, Option<usize>);
97
98impl SvgSink {
99 pub fn new() -> Self {
102 Self::default()
103 }
104
105 pub fn with_precision(precision: usize) -> Self {
108 Self(String::default(), Some(precision))
109 }
110
111 pub fn clear(&mut self) {
113 self.0.clear();
114 }
115
116 fn maybe_push_space(&mut self) {
117 if !self.0.is_empty() {
118 self.0.push(' ');
119 }
120 }
121}
122
123impl core::ops::Deref for SvgSink {
124 type Target = str;
125
126 fn deref(&self) -> &Self::Target {
127 self.0.as_str()
128 }
129}
130
131impl PathSink for SvgSink {
132 fn move_to(&mut self, x: f32, y: f32) {
133 self.maybe_push_space();
134 let _ = if let Some(prec) = self.1 {
135 write!(self.0, "M{x:.0$},{y:.0$}", prec)
136 } else {
137 write!(self.0, "M{x},{y}")
138 };
139 }
140
141 fn line_to(&mut self, x: f32, y: f32) {
142 self.maybe_push_space();
143 let _ = if let Some(prec) = self.1 {
144 write!(self.0, "L{x:.0$},{y:.0$}", prec)
145 } else {
146 write!(self.0, "L{x},{y}")
147 };
148 }
149
150 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
151 self.maybe_push_space();
152 let _ = if let Some(prec) = self.1 {
153 write!(self.0, "Q{cx0:.0$},{cy0:.0$} {x:.0$},{y:.0$}", prec)
154 } else {
155 write!(self.0, "Q{cx0},{cy0} {x},{y}")
156 };
157 }
158
159 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
160 self.maybe_push_space();
161 let _ = if let Some(prec) = self.1 {
162 write!(
163 self.0,
164 "C{cx0:.0$},{cy0:.0$} {cx1:.0$},{cy1:.0$} {x:.0$},{y:.0$}",
165 prec
166 )
167 } else {
168 write!(self.0, "C{cx0},{cy0} {cx1},{cy1} {x},{y}")
169 };
170 }
171
172 fn close(&mut self) {
173 self.maybe_push_space();
174 self.0.push('Z');
175 }
176}
177
178impl AsRef<str> for SvgSink {
179 fn as_ref(&self) -> &str {
180 self.0.as_ref()
181 }
182}
183
184impl From<String> for SvgSink {
185 fn from(value: String) -> Self {
186 Self(value, None)
187 }
188}
189
190impl From<SvgSink> for String {
191 fn from(value: SvgSink) -> Self {
192 value.0
193 }
194}
195
196impl fmt::Display for SvgSink {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 write!(f, "{}", self.0)
199 }
200}
201
202#[derive(Clone, Default, Debug)]
204pub struct ControlBoundsSink(Option<BoundingBox<f32>>);
205
206impl ControlBoundsSink {
207 pub fn new() -> Self {
209 Self(None)
210 }
211
212 pub fn bounding_box(&self) -> Option<BoundingBox<f32>> {
214 self.0
215 }
216
217 fn update_bounds(&mut self, x: f32, y: f32) {
218 if let Some(bb) = &mut self.0 {
219 bb.x_min = bb.x_min.min(x);
220 bb.y_min = bb.y_min.min(y);
221 bb.x_max = bb.x_max.max(x);
222 bb.y_max = bb.y_max.max(y);
223 } else {
224 self.0 = Some(BoundingBox {
225 x_min: x,
226 y_min: y,
227 x_max: x,
228 y_max: y,
229 });
230 }
231 }
232}
233
234impl PathSink for ControlBoundsSink {
235 fn move_to(&mut self, x: f32, y: f32) {
236 self.update_bounds(x, y);
237 }
238
239 fn line_to(&mut self, x: f32, y: f32) {
240 self.update_bounds(x, y);
241 }
242
243 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
244 self.update_bounds(cx0, cy0);
245 self.update_bounds(x, y);
246 }
247
248 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
249 self.update_bounds(cx0, cy0);
250 self.update_bounds(cx1, cy1);
251 self.update_bounds(x, y);
252 }
253
254 fn close(&mut self) {}
255}
256
257#[derive(Clone, Debug)]
260pub struct TransformSink<S> {
261 sink: S,
262 transform: Matrix<f32>,
263}
264
265impl<S> TransformSink<S> {
266 pub fn new(sink: S, transform: Matrix<f32>) -> Self {
269 Self { sink, transform }
270 }
271
272 pub fn transform(&self) -> Matrix<f32> {
274 self.transform
275 }
276
277 pub fn into_inner(self) -> S {
279 self.sink
280 }
281}
282
283impl<S: PathSink> PathSink for TransformSink<S> {
284 fn move_to(&mut self, x: f32, y: f32) {
285 let (x, y) = self.transform.transform(x, y);
286 self.sink.move_to(x, y);
287 }
288
289 fn line_to(&mut self, x: f32, y: f32) {
290 let (x, y) = self.transform.transform(x, y);
291 self.sink.line_to(x, y);
292 }
293
294 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
295 let (cx0, cy0) = self.transform.transform(cx0, cy0);
296 let (x, y) = self.transform.transform(x, y);
297 self.sink.quad_to(cx0, cy0, x, y);
298 }
299
300 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
301 let (cx0, cy0) = self.transform.transform(cx0, cy0);
302 let (cx1, cy1) = self.transform.transform(cx1, cy1);
303 let (x, y) = self.transform.transform(x, y);
304 self.sink.curve_to(cx0, cy0, cx1, cy1, x, y);
305 }
306
307 fn close(&mut self) {
308 self.sink.close();
309 }
310}
311
312impl<P: PathSink + ?Sized> PathSink for &mut P {
313 fn move_to(&mut self, x: f32, y: f32) {
314 (**self).move_to(x, y)
315 }
316
317 fn line_to(&mut self, x: f32, y: f32) {
318 (**self).line_to(x, y)
319 }
320
321 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
322 (**self).quad_to(cx0, cy0, x, y)
323 }
324
325 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
326 (**self).curve_to(cx0, cy0, cx1, cy1, x, y)
327 }
328
329 fn close(&mut self) {
330 (**self).close()
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn transform_sink_moves_every_point() {
340 let transform = Matrix::from_elements([2.0, 1.0, 0.0, 1.0, 10.0, 20.0]);
342 let mut path = Vec::new();
343 let mut sink = TransformSink::new(&mut path, transform);
344 sink.move_to(1.0, 2.0);
345 sink.line_to(3.0, 4.0);
346 sink.quad_to(1.0, 0.0, 0.0, 1.0);
347 sink.curve_to(1.0, 1.0, 2.0, 2.0, 3.0, 3.0);
348 sink.close();
349 assert_eq!(sink.transform(), transform);
350 assert_eq!(
351 path,
352 [
353 PathElement::MoveTo { x: 12.0, y: 23.0 },
354 PathElement::LineTo { x: 16.0, y: 27.0 },
355 PathElement::QuadTo {
356 cx0: 12.0,
357 cy0: 21.0,
358 x: 10.0,
359 y: 21.0
360 },
361 PathElement::CurveTo {
362 cx0: 12.0,
363 cy0: 22.0,
364 cx1: 14.0,
365 cy1: 24.0,
366 x: 16.0,
367 y: 26.0
368 },
369 PathElement::Close,
370 ]
371 );
372 }
373
374 #[test]
375 fn svg_sink_precision() {
376 let svg_data = [None, Some(1), Some(4)].map(|prec| {
377 let mut pen = match prec {
378 None => SvgSink::new(),
379 Some(prec) => SvgSink::with_precision(prec),
380 };
381 pen.move_to(1.0, 2.45556);
382 pen.line_to(1.2, 4.0);
383 pen.quad_to(2.0345, 3.56789, -0.157, -425.07);
384 pen.curve_to(-37.0010, 4.5, 2.0, 1.0, -0.5, -0.25);
385 pen.close();
386 pen.to_string()
387 });
388 let expected = [
389 "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",
390 "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",
391 "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"
392 ];
393 for (result, expected) in svg_data.iter().zip(&expected) {
394 assert_eq!(result, expected);
395 }
396 }
397}