1use alloc::{string::String, vec::Vec};
4use core::fmt::{self, Write};
5
6use crate::metrics::BoundingBox;
7
8pub trait OutlinePen {
10 fn move_to(&mut self, x: f32, y: f32);
12
13 fn line_to(&mut self, x: f32, y: f32);
15
16 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32);
19
20 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32);
23
24 fn close(&mut self);
26}
27
28#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
30pub enum PathElement {
31 MoveTo { x: f32, y: f32 },
33 LineTo { x: f32, y: f32 },
35 QuadTo { cx0: f32, cy0: f32, x: f32, y: f32 },
38 CurveTo {
41 cx0: f32,
42 cy0: f32,
43 cx1: f32,
44 cy1: f32,
45 x: f32,
46 y: f32,
47 },
48 Close,
50}
51
52#[derive(Debug, Default, Copy, Clone)]
60pub enum PathStyle {
61 #[default]
65 FreeType,
66 HarfBuzz,
72}
73
74impl OutlinePen for Vec<PathElement> {
75 fn move_to(&mut self, x: f32, y: f32) {
76 self.push(PathElement::MoveTo { x, y })
77 }
78
79 fn line_to(&mut self, x: f32, y: f32) {
80 self.push(PathElement::LineTo { x, y })
81 }
82
83 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
84 self.push(PathElement::QuadTo { cx0, cy0, x, y })
85 }
86
87 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
88 self.push(PathElement::CurveTo {
89 cx0,
90 cy0,
91 cx1,
92 cy1,
93 x,
94 y,
95 })
96 }
97
98 fn close(&mut self) {
99 self.push(PathElement::Close)
100 }
101}
102
103pub struct NullPen;
105
106impl OutlinePen for NullPen {
107 fn move_to(&mut self, _x: f32, _y: f32) {}
108 fn line_to(&mut self, _x: f32, _y: f32) {}
109 fn quad_to(&mut self, _cx0: f32, _cy0: f32, _x: f32, _y: f32) {}
110 fn curve_to(&mut self, _cx0: f32, _cy0: f32, _cx1: f32, _cy1: f32, _x: f32, _y: f32) {}
111 fn close(&mut self) {}
112}
113
114#[derive(Clone, Default, Debug)]
116pub struct SvgPen(String, Option<usize>);
117
118impl SvgPen {
119 pub fn new() -> Self {
122 Self::default()
123 }
124
125 pub fn with_precision(precision: usize) -> Self {
128 Self(String::default(), Some(precision))
129 }
130
131 pub fn clear(&mut self) {
133 self.0.clear();
134 }
135
136 fn maybe_push_space(&mut self) {
137 if !self.0.is_empty() {
138 self.0.push(' ');
139 }
140 }
141}
142
143impl core::ops::Deref for SvgPen {
144 type Target = str;
145
146 fn deref(&self) -> &Self::Target {
147 self.0.as_str()
148 }
149}
150
151impl OutlinePen for SvgPen {
152 fn move_to(&mut self, x: f32, y: f32) {
153 self.maybe_push_space();
154 let _ = if let Some(prec) = self.1 {
155 write!(self.0, "M{x:.0$},{y:.0$}", prec)
156 } else {
157 write!(self.0, "M{x},{y}")
158 };
159 }
160
161 fn line_to(&mut self, x: f32, y: f32) {
162 self.maybe_push_space();
163 let _ = if let Some(prec) = self.1 {
164 write!(self.0, "L{x:.0$},{y:.0$}", prec)
165 } else {
166 write!(self.0, "L{x},{y}")
167 };
168 }
169
170 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
171 self.maybe_push_space();
172 let _ = if let Some(prec) = self.1 {
173 write!(self.0, "Q{cx0:.0$},{cy0:.0$} {x:.0$},{y:.0$}", prec)
174 } else {
175 write!(self.0, "Q{cx0},{cy0} {x},{y}")
176 };
177 }
178
179 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
180 self.maybe_push_space();
181 let _ = if let Some(prec) = self.1 {
182 write!(
183 self.0,
184 "C{cx0:.0$},{cy0:.0$} {cx1:.0$},{cy1:.0$} {x:.0$},{y:.0$}",
185 prec
186 )
187 } else {
188 write!(self.0, "C{cx0},{cy0} {cx1},{cy1} {x},{y}")
189 };
190 }
191
192 fn close(&mut self) {
193 self.maybe_push_space();
194 self.0.push('Z');
195 }
196}
197
198impl AsRef<str> for SvgPen {
199 fn as_ref(&self) -> &str {
200 self.0.as_ref()
201 }
202}
203
204impl From<String> for SvgPen {
205 fn from(value: String) -> Self {
206 Self(value, None)
207 }
208}
209
210impl From<SvgPen> for String {
211 fn from(value: SvgPen) -> Self {
212 value.0
213 }
214}
215
216impl fmt::Display for SvgPen {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 write!(f, "{}", self.0)
219 }
220}
221
222#[derive(Clone, Default, Debug)]
224pub struct ControlBoundsPen(Option<BoundingBox>);
225impl ControlBoundsPen {
226 pub fn new() -> Self {
228 Self(None)
229 }
230
231 pub fn bounding_box(&self) -> Option<BoundingBox> {
233 self.0
234 }
235
236 fn update_bounds(&mut self, x: f32, y: f32) {
237 if let Some(bb) = &mut self.0 {
238 bb.x_min = bb.x_min.min(x);
239 bb.y_min = bb.y_min.min(y);
240 bb.x_max = bb.x_max.max(x);
241 bb.y_max = bb.y_max.max(y);
242 } else {
243 self.0 = Some(BoundingBox {
244 x_min: x,
245 y_min: y,
246 x_max: x,
247 y_max: y,
248 });
249 }
250 }
251}
252
253impl OutlinePen for ControlBoundsPen {
254 fn move_to(&mut self, x: f32, y: f32) {
255 self.update_bounds(x, y);
256 }
257
258 fn line_to(&mut self, x: f32, y: f32) {
259 self.update_bounds(x, y);
260 }
261
262 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
263 self.update_bounds(cx0, cy0);
264 self.update_bounds(x, y);
265 }
266
267 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
268 self.update_bounds(cx0, cy0);
269 self.update_bounds(cx1, cy1);
270 self.update_bounds(x, y);
271 }
272
273 fn close(&mut self) {}
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn svg_pen_precision() {
282 let svg_data = [None, Some(1), Some(4)].map(|prec| {
283 let mut pen = match prec {
284 None => SvgPen::new(),
285 Some(prec) => SvgPen::with_precision(prec),
286 };
287 pen.move_to(1.0, 2.45556);
288 pen.line_to(1.2, 4.0);
289 pen.quad_to(2.0345, 3.56789, -0.157, -425.07);
290 pen.curve_to(-37.0010, 4.5, 2.0, 1.0, -0.5, -0.25);
291 pen.close();
292 pen.to_string()
293 });
294 let expected = [
295 "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",
296 "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",
297 "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"
298 ];
299 for (result, expected) in svg_data.iter().zip(&expected) {
300 assert_eq!(result, expected);
301 }
302 }
303}