1use alloc::{string::String, vec::Vec};
4use core::fmt::{self, Write};
5
6pub trait OutlinePen {
8 fn move_to(&mut self, x: f32, y: f32);
10
11 fn line_to(&mut self, x: f32, y: f32);
13
14 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32);
17
18 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32);
21
22 fn close(&mut self);
24}
25
26#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
28pub enum PathElement {
29 MoveTo { x: f32, y: f32 },
31 LineTo { x: f32, y: f32 },
33 QuadTo { cx0: f32, cy0: f32, x: f32, y: f32 },
36 CurveTo {
39 cx0: f32,
40 cy0: f32,
41 cx1: f32,
42 cy1: f32,
43 x: f32,
44 y: f32,
45 },
46 Close,
48}
49
50#[derive(Debug, Default, Copy, Clone)]
58pub enum PathStyle {
59 #[default]
63 FreeType,
64 HarfBuzz,
70}
71
72impl OutlinePen for Vec<PathElement> {
73 fn move_to(&mut self, x: f32, y: f32) {
74 self.push(PathElement::MoveTo { x, y })
75 }
76
77 fn line_to(&mut self, x: f32, y: f32) {
78 self.push(PathElement::LineTo { x, y })
79 }
80
81 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
82 self.push(PathElement::QuadTo { cx0, cy0, x, y })
83 }
84
85 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
86 self.push(PathElement::CurveTo {
87 cx0,
88 cy0,
89 cx1,
90 cy1,
91 x,
92 y,
93 })
94 }
95
96 fn close(&mut self) {
97 self.push(PathElement::Close)
98 }
99}
100
101pub struct NullPen;
103
104impl OutlinePen for NullPen {
105 fn move_to(&mut self, _x: f32, _y: f32) {}
106 fn line_to(&mut self, _x: f32, _y: f32) {}
107 fn quad_to(&mut self, _cx0: f32, _cy0: f32, _x: f32, _y: f32) {}
108 fn curve_to(&mut self, _cx0: f32, _cy0: f32, _cx1: f32, _cy1: f32, _x: f32, _y: f32) {}
109 fn close(&mut self) {}
110}
111
112#[derive(Clone, Default, Debug)]
114pub struct SvgPen(String, Option<usize>);
115
116impl SvgPen {
117 pub fn new() -> Self {
120 Self::default()
121 }
122
123 pub fn with_precision(precision: usize) -> Self {
126 Self(String::default(), Some(precision))
127 }
128
129 pub fn clear(&mut self) {
131 self.0.clear();
132 }
133
134 fn maybe_push_space(&mut self) {
135 if !self.0.is_empty() {
136 self.0.push(' ');
137 }
138 }
139}
140
141impl core::ops::Deref for SvgPen {
142 type Target = str;
143
144 fn deref(&self) -> &Self::Target {
145 self.0.as_str()
146 }
147}
148
149impl OutlinePen for SvgPen {
150 fn move_to(&mut self, x: f32, y: f32) {
151 self.maybe_push_space();
152 let _ = if let Some(prec) = self.1 {
153 write!(self.0, "M{x:.0$},{y:.0$}", prec)
154 } else {
155 write!(self.0, "M{x},{y}")
156 };
157 }
158
159 fn line_to(&mut self, x: f32, y: f32) {
160 self.maybe_push_space();
161 let _ = if let Some(prec) = self.1 {
162 write!(self.0, "L{x:.0$},{y:.0$}", prec)
163 } else {
164 write!(self.0, "L{x},{y}")
165 };
166 }
167
168 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
169 self.maybe_push_space();
170 let _ = if let Some(prec) = self.1 {
171 write!(self.0, "Q{cx0:.0$},{cy0:.0$} {x:.0$},{y:.0$}", prec)
172 } else {
173 write!(self.0, "Q{cx0},{cy0} {x},{y}")
174 };
175 }
176
177 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
178 self.maybe_push_space();
179 let _ = if let Some(prec) = self.1 {
180 write!(
181 self.0,
182 "C{cx0:.0$},{cy0:.0$} {cx1:.0$},{cy1:.0$} {x:.0$},{y:.0$}",
183 prec
184 )
185 } else {
186 write!(self.0, "C{cx0},{cy0} {cx1},{cy1} {x},{y}")
187 };
188 }
189
190 fn close(&mut self) {
191 self.maybe_push_space();
192 self.0.push('Z');
193 }
194}
195
196impl AsRef<str> for SvgPen {
197 fn as_ref(&self) -> &str {
198 self.0.as_ref()
199 }
200}
201
202impl From<String> for SvgPen {
203 fn from(value: String) -> Self {
204 Self(value, None)
205 }
206}
207
208impl From<SvgPen> for String {
209 fn from(value: SvgPen) -> Self {
210 value.0
211 }
212}
213
214impl fmt::Display for SvgPen {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 write!(f, "{}", self.0)
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn svg_pen_precision() {
226 let svg_data = [None, Some(1), Some(4)].map(|prec| {
227 let mut pen = match prec {
228 None => SvgPen::new(),
229 Some(prec) => SvgPen::with_precision(prec),
230 };
231 pen.move_to(1.0, 2.45556);
232 pen.line_to(1.2, 4.0);
233 pen.quad_to(2.0345, 3.56789, -0.157, -425.07);
234 pen.curve_to(-37.0010, 4.5, 2.0, 1.0, -0.5, -0.25);
235 pen.close();
236 pen.to_string()
237 });
238 let expected = [
239 "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",
240 "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",
241 "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"
242 ];
243 for (result, expected) in svg_data.iter().zip(&expected) {
244 assert_eq!(result, expected);
245 }
246 }
247}