Skip to main content

read_fonts/model/
pen.rs

1//! Types for collecting the output when drawing a glyph outline.
2
3use alloc::{string::String, vec::Vec};
4use core::fmt::{self, Write};
5use types::BoundingBox;
6
7/// Interface for accepting a sequence of path commands.
8pub trait OutlinePen {
9    /// Emit a command to begin a new subpath at (x, y).
10    fn move_to(&mut self, x: f32, y: f32);
11
12    /// Emit a line segment from the current point to (x, y).
13    fn line_to(&mut self, x: f32, y: f32);
14
15    /// Emit a quadratic bezier segment from the current point with a control
16    /// point at (cx0, cy0) and ending at (x, y).
17    fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32);
18
19    /// Emit a cubic bezier segment from the current point with control
20    /// points at (cx0, cy0) and (cx1, cy1) and ending at (x, y).
21    fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32);
22
23    /// Emit a command to close the current subpath.
24    fn close(&mut self);
25}
26
27/// Single element of a path.
28#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
29pub enum PathElement {
30    /// Begin a new subpath at (x, y).
31    MoveTo { x: f32, y: f32 },
32    /// Draw a line from the current point to (x, y).
33    LineTo { x: f32, y: f32 },
34    /// Draw a quadratic bezier from the current point with a control point at
35    /// (cx0, cy0) and ending at (x, y).
36    QuadTo { cx0: f32, cy0: f32, x: f32, y: f32 },
37    /// Draw a cubic bezier from the current point with control points at
38    /// (cx0, cy0) and (cx1, cy1) and ending at (x, y).
39    CurveTo {
40        cx0: f32,
41        cy0: f32,
42        cx1: f32,
43        cy1: f32,
44        x: f32,
45        y: f32,
46    },
47    /// Close the current subpath.
48    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
80/// Pen that drops all drawing output into the ether.
81pub 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/// Pen that generates SVG style path data.
92#[derive(Clone, Default, Debug)]
93pub struct SvgPen(String, Option<usize>);
94
95impl SvgPen {
96    /// Creates a new SVG pen that formats floating point values with the
97    /// standard behavior.
98    pub fn new() -> Self {
99        Self::default()
100    }
101
102    /// Creates a new SVG pen with the given precision (the number of digits
103    /// that will be printed after the decimal).
104    pub fn with_precision(precision: usize) -> Self {
105        Self(String::default(), Some(precision))
106    }
107
108    /// Clears the content of the internal string.
109    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/// Pen that generates the control bounds of a glyph outline.
200#[derive(Clone, Default, Debug)]
201pub struct ControlBoundsPen(Option<BoundingBox<f32>>);
202
203impl ControlBoundsPen {
204    /// Creates a new bounds pen.
205    pub fn new() -> Self {
206        Self(None)
207    }
208
209    /// Returns the bounding box collected by this pen.
210    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}