Skip to main content

read_fonts/model/glyph/outline/
path.rs

1//! Path commands for outlines and sinks that receive them.
2
3use alloc::{string::String, vec::Vec};
4use core::fmt::{self, Write};
5use types::{BoundingBox, Matrix};
6
7/// Receives the path commands for an outline.
8///
9/// To receive the outline's points in font units, use
10/// [`UnscaledOutlineSink`](super::UnscaledOutlineSink).
11pub trait PathSink {
12    /// Emit a command to begin a new subpath at (x, y).
13    fn move_to(&mut self, x: f32, y: f32);
14
15    /// Emit a line segment from the current point to (x, y).
16    fn line_to(&mut self, x: f32, y: f32);
17
18    /// Emit a quadratic bezier segment from the current point with a control
19    /// point at (cx0, cy0) and ending at (x, y).
20    fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32);
21
22    /// Emit a cubic bezier segment from the current point with control
23    /// points at (cx0, cy0) and (cx1, cy1) and ending at (x, y).
24    fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32);
25
26    /// Emit a command to close the current subpath.
27    fn close(&mut self);
28}
29
30/// Single element of a path.
31#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
32pub enum PathElement {
33    /// Begin a new subpath at (x, y).
34    MoveTo { x: f32, y: f32 },
35    /// Draw a line from the current point to (x, y).
36    LineTo { x: f32, y: f32 },
37    /// Draw a quadratic bezier from the current point with a control point at
38    /// (cx0, cy0) and ending at (x, y).
39    QuadTo { cx0: f32, cy0: f32, x: f32, y: f32 },
40    /// Draw a cubic bezier from the current point with control points at
41    /// (cx0, cy0) and (cx1, cy1) and ending at (x, y).
42    CurveTo {
43        cx0: f32,
44        cy0: f32,
45        cx1: f32,
46        cy1: f32,
47        x: f32,
48        y: f32,
49    },
50    /// Close the current subpath.
51    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
83/// A sink that discards all drawing commands.
84pub 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/// A sink that writes SVG style path data.
95#[derive(Clone, Default, Debug)]
96pub struct SvgSink(String, Option<usize>);
97
98impl SvgSink {
99    /// Creates a sink that formats floating point values with the
100    /// standard behavior.
101    pub fn new() -> Self {
102        Self::default()
103    }
104
105    /// Creates a sink with the given precision (the number of digits
106    /// that will be printed after the decimal).
107    pub fn with_precision(precision: usize) -> Self {
108        Self(String::default(), Some(precision))
109    }
110
111    /// Clears the content of the internal string.
112    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/// A sink that collects the control bounds of a glyph outline.
203#[derive(Clone, Default, Debug)]
204pub struct ControlBoundsSink(Option<BoundingBox<f32>>);
205
206impl ControlBoundsSink {
207    /// Creates an empty sink.
208    pub fn new() -> Self {
209        Self(None)
210    }
211
212    /// Returns the bounding box collected by this sink.
213    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/// A sink that applies an affine transform to every point and forwards the
258/// result to another sink.
259#[derive(Clone, Debug)]
260pub struct TransformSink<S> {
261    sink: S,
262    transform: Matrix<f32>,
263}
264
265impl<S> TransformSink<S> {
266    /// Creates a sink that applies `transform` to each point and forwards
267    /// it to `sink`.
268    pub fn new(sink: S, transform: Matrix<f32>) -> Self {
269        Self { sink, transform }
270    }
271
272    /// Returns the transform applied to every point.
273    pub fn transform(&self) -> Matrix<f32> {
274        self.transform
275    }
276
277    /// Consumes this sink and returns the inner sink.
278    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        // Scale x by 2, shear y by x, and move by (10, 20).
341        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}