allsorts_subset_browser/tables/glyf/
outline.rs

1use pathfinder_geometry::transform2d::{Matrix2x2F, Transform2F};
2use pathfinder_geometry::vector::Vector2F;
3
4use crate::error::ParseError;
5use crate::outline::{OutlineBuilder, OutlineSink};
6use crate::tables::glyf::{
7    CompositeGlyphComponent, CompositeGlyphScale, GlyfTable, Glyph, SimpleGlyph,
8    COMPOSITE_GLYPH_RECURSION_LIMIT,
9};
10
11use contour::{Contour, CurvePoint};
12
13impl<'a> GlyfTable<'a> {
14    fn visit_outline<S: OutlineSink>(
15        &mut self,
16        glyph_index: u16,
17        sink: &mut S,
18        offset: Vector2F,
19        scale: Option<CompositeGlyphScale>,
20        depth: u8,
21    ) -> Result<(), ParseError> {
22        if depth > COMPOSITE_GLYPH_RECURSION_LIMIT {
23            return Err(ParseError::LimitExceeded);
24        }
25
26        let glyph = self.get_parsed_glyph(glyph_index)?;
27        let scale = scale.map_or(Matrix2x2F::from_scale(1.0), Matrix2x2F::from);
28        let transform = Transform2F {
29            vector: offset,
30            matrix: scale,
31        };
32
33        match &glyph {
34            Glyph::Empty(_) => Ok(()),
35            Glyph::Simple(simple_glyph) => {
36                Self::visit_simple_glyph_outline(sink, transform, simple_glyph)
37            }
38            Glyph::Composite(composite) => {
39                // Have to clone glyphs otherwise glyph is mutably borrowed as &mut self as well
40                // as borrowed via the `glyphs` argument.
41                let glyphs = composite.glyphs.clone();
42                self.visit_composite_glyph_outline(sink, &glyphs, depth)
43            }
44        }
45    }
46
47    fn visit_simple_glyph_outline<S: OutlineSink>(
48        sink: &mut S,
49        transform: Transform2F,
50        simple_glyph: &SimpleGlyph<'_>,
51    ) -> Result<(), ParseError> {
52        for points_and_flags in simple_glyph.contours() {
53            let contour = Contour::new(points_and_flags);
54
55            // Determine origin of the contour and move to it
56            let origin = contour.origin();
57            sink.move_to(transform * origin);
58
59            // Consume the stream of points...
60            let mut points = contour.points();
61            // It's assumed that the current location is on curve each time through this loop
62            while let Some(next) = points.next() {
63                match next {
64                    CurvePoint::OnCurve(to) => {
65                        sink.line_to(transform * to);
66                    }
67                    CurvePoint::Control(control) => {
68                        match points.next() {
69                            Some(CurvePoint::OnCurve(to)) => {
70                                sink.quadratic_curve_to(transform * control, transform * to);
71                            }
72                            Some(CurvePoint::Control(_)) => {
73                                // Can't happen as the Points iterator inserts on curve mid-points
74                                // when two consecutive control points are encountered
75                                unreachable!("consecutive control points")
76                            }
77                            None => {
78                                // Wrap around to the first point
79                                sink.quadratic_curve_to(transform * control, transform * origin);
80                                break;
81                            }
82                        }
83                    }
84                }
85            }
86
87            sink.close();
88        }
89
90        Ok(())
91    }
92
93    fn visit_composite_glyph_outline<S: OutlineSink>(
94        &mut self,
95        sink: &mut S,
96        glyphs: &[CompositeGlyphComponent],
97        depth: u8,
98    ) -> Result<(), ParseError> {
99        for composite_glyph in glyphs {
100            // Argument1 and argument2 can be either x and y offsets to be added to the glyph (the
101            // ARGS_ARE_XY_VALUES flag is set), or two point numbers (the ARGS_ARE_XY_VALUES flag
102            // is not set). In the latter case, the first point number indicates the point that is
103            // to be matched to the new glyph. The second number indicates the new glyph’s
104            // “matched” point. Once a glyph is added, its point numbers begin directly after the
105            // last glyphs (endpoint of first glyph + 1).
106            //
107            // https://docs.microsoft.com/en-us/typography/opentype/spec/glyf#composite-glyph-description
108            let offset = if composite_glyph.flags.args_are_xy_values() {
109                // NOTE: Casts are safe as max value of composite glyph is u16::MAX
110                Vector2F::new(
111                    i32::from(composite_glyph.argument1) as f32,
112                    i32::from(composite_glyph.argument2) as f32,
113                )
114            } else {
115                // TODO: support args as point numbers
116                Vector2F::zero()
117            };
118
119            self.visit_outline(
120                composite_glyph.glyph_index,
121                sink,
122                offset,
123                composite_glyph.scale,
124                depth + 1,
125            )?;
126        }
127
128        Ok(())
129    }
130}
131
132impl<'a> OutlineBuilder for GlyfTable<'a> {
133    type Error = ParseError;
134
135    fn visit<V: OutlineSink>(
136        &mut self,
137        glyph_index: u16,
138        visitor: &mut V,
139    ) -> Result<(), Self::Error> {
140        self.visit_outline(glyph_index, visitor, Vector2F::new(0., 0.), None, 0)
141    }
142}
143
144mod contour {
145    use crate::tables::glyf::{Point, SimpleGlyphFlag};
146    use pathfinder_geometry::vector::Vector2F;
147
148    pub struct Contour<'points> {
149        points_and_flags: &'points [(SimpleGlyphFlag, Point)],
150    }
151
152    #[derive(Debug, PartialEq)]
153    pub enum CurvePoint {
154        OnCurve(Vector2F),
155        Control(Vector2F),
156    }
157
158    pub struct Points<'a, 'points> {
159        contour: &'a Contour<'points>,
160        i: usize,
161        until: usize,
162        mid: Option<Vector2F>,
163    }
164
165    impl<'points> Contour<'points> {
166        pub fn new(points_and_flags: &'points [(SimpleGlyphFlag, Point)]) -> Self {
167            assert!(points_and_flags.len() > 0);
168            Contour { points_and_flags }
169        }
170
171        pub fn origin(&self) -> Vector2F {
172            self.calculate_origin().0
173        }
174
175        pub fn calculate_origin(&self) -> (Vector2F, usize, usize) {
176            match (self.first(), self.last()) {
177                (CurvePoint::OnCurve(first), _) => {
178                    // Origin is the first point, so start on the second point
179                    (first, 1, self.len())
180                }
181                (CurvePoint::Control(_), CurvePoint::OnCurve(last)) => {
182                    // Origin is the last point, so start on the first point and consider
183                    // the last point already processed
184                    (last, 0, self.len() - 1) // TODO: Test this
185                }
186                (CurvePoint::Control(first), CurvePoint::Control(last)) => {
187                    // Origin is the mid-point between first and last control points.
188                    // Start on the first point
189                    (first.lerp(last, 0.5), 0, self.len())
190                }
191            }
192        }
193
194        pub fn points<'a>(&'a self) -> Points<'a, 'points> {
195            let (_, start, until) = self.calculate_origin();
196            Points {
197                contour: self,
198                i: start,
199                until,
200                mid: None,
201            }
202        }
203
204        pub fn first(&self) -> CurvePoint {
205            self.get(0)
206        }
207
208        pub fn last(&self) -> CurvePoint {
209            self.get(self.points_and_flags.len() - 1)
210        }
211
212        pub fn len(&self) -> usize {
213            self.points_and_flags.len()
214        }
215
216        fn get(&self, index: usize) -> CurvePoint {
217            let (flags, point) = self.points_and_flags[index];
218            CurvePoint::new(point, flags.is_on_curve())
219        }
220    }
221
222    impl<'a, 'points> Iterator for Points<'a, 'points> {
223        type Item = CurvePoint;
224
225        fn next(&mut self) -> Option<Self::Item> {
226            if let Some(mid) = self.mid {
227                self.mid = None;
228                return Some(CurvePoint::OnCurve(mid));
229            }
230
231            if self.i >= self.until {
232                return None;
233            }
234
235            let point = match self.contour.get(self.i) {
236                point @ CurvePoint::OnCurve(_) => point,
237                CurvePoint::Control(control) => {
238                    // Check the next point, wrapping around if needed
239                    match self.contour.get((self.i + 1) % self.contour.len()) {
240                        CurvePoint::OnCurve(_) => CurvePoint::Control(control),
241                        CurvePoint::Control(control2) => {
242                            // Next point is a control point, yield mid point as on curve point
243                            // after this one
244                            self.mid = Some(control.lerp(control2, 0.5));
245                            CurvePoint::Control(control)
246                        }
247                    }
248                }
249            };
250
251            self.i += 1;
252            Some(point)
253        }
254    }
255
256    impl CurvePoint {
257        fn new(point: Point, on_curve: bool) -> Self {
258            if on_curve {
259                CurvePoint::OnCurve(Vector2F::from(point))
260            } else {
261                CurvePoint::Control(Vector2F::from(point))
262            }
263        }
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use pathfinder_geometry::line_segment::LineSegment2F;
270    use pathfinder_geometry::vector::vec2f;
271
272    use crate::tables::glyf::tests::{composite_glyph_fixture, simple_glyph_fixture};
273    use crate::tables::glyf::{GlyfRecord, Point, SimpleGlyphFlag};
274
275    use super::*;
276
277    struct TestVisitor {}
278
279    impl OutlineSink for TestVisitor {
280        fn move_to(&mut self, to: Vector2F) {
281            println!("move_to({}, {})", to.x(), to.y());
282        }
283
284        fn line_to(&mut self, to: Vector2F) {
285            println!("line_to({}, {})", to.x(), to.y());
286        }
287
288        fn quadratic_curve_to(&mut self, control: Vector2F, to: Vector2F) {
289            println!(
290                "quad_to({}, {}, {}, {})",
291                control.x(),
292                control.y(),
293                to.x(),
294                to.y()
295            );
296        }
297
298        fn cubic_curve_to(&mut self, control: LineSegment2F, to: Vector2F) {
299            println!(
300                "curve_to({}, {}, {}, {}, {}, {})",
301                control.from_x(),
302                control.from_y(),
303                control.to_x(),
304                control.to_y(),
305                to.x(),
306                to.y()
307            );
308        }
309
310        fn close(&mut self) {
311            println!("close()");
312        }
313    }
314
315    #[test]
316    fn iter_simple_glyph_contours() {
317        let simple_glyph = simple_glyph_fixture();
318        let contours = simple_glyph
319            .contours()
320            .map(|contour| contour.iter().map(|(_, point)| *point).collect::<Vec<_>>())
321            .collect::<Vec<_>>();
322        let expected = &[&[
323            Point(433, 77),
324            Point(499, 30),
325            Point(625, 2),
326            Point(756, -27),
327            Point(915, -31),
328            Point(891, -47),
329            Point(862, -60),
330            Point(832, -73),
331            Point(819, -103),
332        ]];
333        assert_eq!(&contours, expected);
334    }
335
336    #[test]
337    fn iter_points() {
338        let points_and_flags = &[
339            (SimpleGlyphFlag::ON_CURVE_POINT, Point::zero()),
340            (SimpleGlyphFlag::empty(), Point(10, 40)), // control
341            (SimpleGlyphFlag::empty(), Point(30, 40)), // control
342            (SimpleGlyphFlag::ON_CURVE_POINT, Point(40, 10)),
343        ];
344        let contour = Contour::new(points_and_flags);
345        let points = contour.points().collect::<Vec<_>>();
346        let expected = &[
347            CurvePoint::Control(vec2f(10., 40.)),
348            CurvePoint::OnCurve(vec2f(20., 40.)), // mid point
349            CurvePoint::Control(vec2f(30., 40.)),
350            CurvePoint::OnCurve(vec2f(40., 10.)),
351        ];
352        assert_eq!(contour.origin(), vec2f(0., 0.));
353        assert_eq!(&points, expected);
354    }
355
356    #[test]
357    fn outlines() {
358        let mut glyf = GlyfTable {
359            records: vec![
360                GlyfRecord::Parsed(Glyph::Simple(simple_glyph_fixture())),
361                GlyfRecord::Parsed(Glyph::Composite(composite_glyph_fixture(&[]))),
362                GlyfRecord::Parsed(Glyph::Simple(simple_glyph_fixture())),
363                GlyfRecord::Parsed(Glyph::Simple(simple_glyph_fixture())),
364                GlyfRecord::Parsed(Glyph::Simple(simple_glyph_fixture())),
365                GlyfRecord::Parsed(Glyph::Simple(simple_glyph_fixture())),
366            ],
367        };
368        let mut visitor = TestVisitor {};
369        glyf.visit(1, &mut visitor)
370            .expect("error visiting glyph outline");
371    }
372}