skrifa/outline/
unscaled.rs

1//! Compact representation of an unscaled, unhinted outline.
2
3#![allow(dead_code)]
4
5use super::DrawError;
6use crate::collections::SmallVec;
7use core::ops::Range;
8use raw::{
9    tables::glyf::PointFlags,
10    types::{F26Dot6, Point},
11};
12
13#[derive(Copy, Clone, Default, Debug)]
14pub(super) struct UnscaledPoint {
15    pub x: i16,
16    pub y: i16,
17    pub flags: PointFlags,
18    pub is_contour_start: bool,
19}
20
21impl UnscaledPoint {
22    pub fn from_glyf_point(
23        point: Point<F26Dot6>,
24        flags: PointFlags,
25        is_contour_start: bool,
26    ) -> Self {
27        let point = point.map(|x| (x.to_bits() >> 6) as i16);
28        Self {
29            x: point.x,
30            y: point.y,
31            flags: flags.without_markers(),
32            is_contour_start,
33        }
34    }
35
36    pub fn is_on_curve(self) -> bool {
37        self.flags.is_on_curve()
38    }
39}
40
41pub(super) trait UnscaledOutlineSink {
42    fn try_reserve(&mut self, additional: usize) -> Result<(), DrawError>;
43    fn push(&mut self, point: UnscaledPoint) -> Result<(), DrawError>;
44    fn extend(&mut self, points: impl IntoIterator<Item = UnscaledPoint>) -> Result<(), DrawError> {
45        for point in points.into_iter() {
46            self.push(point)?;
47        }
48        Ok(())
49    }
50}
51
52// please can I have smallvec?
53pub(super) struct UnscaledOutlineBuf<const INLINE_CAP: usize>(SmallVec<UnscaledPoint, INLINE_CAP>);
54
55impl<const INLINE_CAP: usize> UnscaledOutlineBuf<INLINE_CAP> {
56    pub fn new() -> Self {
57        Self(SmallVec::new())
58    }
59
60    pub fn clear(&mut self) {
61        self.0.clear();
62    }
63
64    pub fn as_ref(&self) -> UnscaledOutlineRef {
65        UnscaledOutlineRef {
66            points: self.0.as_slice(),
67        }
68    }
69}
70
71impl<const INLINE_CAP: usize> UnscaledOutlineSink for UnscaledOutlineBuf<INLINE_CAP> {
72    fn try_reserve(&mut self, additional: usize) -> Result<(), DrawError> {
73        if !self.0.try_reserve(additional) {
74            Err(DrawError::InsufficientMemory)
75        } else {
76            Ok(())
77        }
78    }
79
80    fn push(&mut self, point: UnscaledPoint) -> Result<(), DrawError> {
81        self.0.push(point);
82        Ok(())
83    }
84}
85
86#[derive(Copy, Clone, Debug)]
87pub(super) struct UnscaledOutlineRef<'a> {
88    pub points: &'a [UnscaledPoint],
89}
90
91impl UnscaledOutlineRef<'_> {
92    /// Returns the range of contour points and the index of the point within
93    /// that contour for the last point where `f` returns true.
94    ///
95    /// This is common code used for finding extrema when materializing blue
96    /// zones.
97    ///
98    /// For example: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L509>
99    pub fn find_last_contour(
100        &self,
101        mut f: impl FnMut(&UnscaledPoint) -> bool,
102    ) -> Option<(Range<usize>, usize)> {
103        if self.points.is_empty() {
104            return None;
105        }
106        let mut best_contour = 0..0;
107        // Index of the best point relative to the start of the best contour
108        let mut best_point = 0;
109        let mut cur_contour = 0..0;
110        let mut found_best_in_cur_contour = false;
111        for (point_ix, point) in self.points.iter().enumerate() {
112            if point.is_contour_start {
113                if found_best_in_cur_contour {
114                    best_contour = cur_contour;
115                }
116                cur_contour = point_ix..point_ix;
117                found_best_in_cur_contour = false;
118                // Ignore single point contours
119                match self.points.get(point_ix + 1) {
120                    Some(next_point) if next_point.is_contour_start => continue,
121                    None => continue,
122                    _ => {}
123                }
124            }
125            cur_contour.end += 1;
126            if f(point) {
127                best_point = point_ix - cur_contour.start;
128                found_best_in_cur_contour = true;
129            }
130        }
131        if found_best_in_cur_contour {
132            best_contour = cur_contour;
133        }
134        if !best_contour.is_empty() {
135            Some((best_contour, best_point))
136        } else {
137            None
138        }
139    }
140}
141
142/// Adapts an UnscaledOutlineSink to be fed from a pen while tracking
143/// memory allocation errors.
144pub(super) struct UnscaledPenAdapter<'a, T> {
145    sink: &'a mut T,
146    failed: bool,
147}
148
149impl<'a, T> UnscaledPenAdapter<'a, T> {
150    pub fn new(sink: &'a mut T) -> Self {
151        Self {
152            sink,
153            failed: false,
154        }
155    }
156
157    pub fn finish(self) -> Result<(), DrawError> {
158        if self.failed {
159            Err(DrawError::InsufficientMemory)
160        } else {
161            Ok(())
162        }
163    }
164}
165
166impl<T> UnscaledPenAdapter<'_, T>
167where
168    T: UnscaledOutlineSink,
169{
170    fn push(&mut self, x: f32, y: f32, flags: PointFlags, is_contour_start: bool) {
171        if self
172            .sink
173            .push(UnscaledPoint {
174                x: x as i16,
175                y: y as i16,
176                flags,
177                is_contour_start,
178            })
179            .is_err()
180        {
181            self.failed = true;
182        }
183    }
184}
185
186impl<T: UnscaledOutlineSink> super::OutlinePen for UnscaledPenAdapter<'_, T> {
187    fn move_to(&mut self, x: f32, y: f32) {
188        self.push(x, y, PointFlags::on_curve(), true);
189    }
190
191    fn line_to(&mut self, x: f32, y: f32) {
192        self.push(x, y, PointFlags::on_curve(), false);
193    }
194
195    fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
196        self.push(cx0, cy0, PointFlags::off_curve_quad(), false);
197        self.push(x, y, PointFlags::on_curve(), false);
198    }
199
200    fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
201        self.push(cx0, cy0, PointFlags::off_curve_cubic(), false);
202        self.push(cx1, cy1, PointFlags::off_curve_cubic(), false);
203        self.push(x, y, PointFlags::on_curve(), false);
204    }
205
206    fn close(&mut self) {}
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use crate::{prelude::LocationRef, MetadataProvider};
213    use raw::{types::GlyphId, FontRef};
214
215    #[test]
216    fn read_glyf_outline() {
217        let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
218        let glyph = font.outline_glyphs().get(GlyphId::new(5)).unwrap();
219        let mut outline = UnscaledOutlineBuf::<32>::new();
220        glyph
221            .draw_unscaled(LocationRef::default(), None, &mut outline)
222            .unwrap();
223        let outline = outline.as_ref();
224        let expected = [
225            // contour 0
226            (400, 80, 1),
227            (400, 360, 1),
228            (320, 360, 1),
229            (320, 600, 1),
230            (320, 633, 0),
231            (367, 680, 0),
232            (400, 680, 1),
233            (560, 680, 1),
234            (593, 680, 0),
235            (640, 633, 0),
236            (640, 600, 1),
237            (640, 360, 1),
238            (560, 360, 1),
239            (560, 80, 1),
240            // contour 1
241            (480, 720, 1),
242            (447, 720, 0),
243            (400, 767, 0),
244            (400, 800, 1),
245            (400, 833, 0),
246            (447, 880, 0),
247            (480, 880, 1),
248            (513, 880, 0),
249            (560, 833, 0),
250            (560, 800, 1),
251            (560, 767, 0),
252            (513, 720, 0),
253        ];
254        let points = outline
255            .points
256            .iter()
257            .map(|point| (point.x, point.y, point.flags.to_bits()))
258            .collect::<Vec<_>>();
259        assert_eq!(points, expected);
260    }
261
262    #[test]
263    #[cfg(feature = "spec_next")]
264    fn read_cubic_glyf_outline() {
265        let font = FontRef::new(font_test_data::CUBIC_GLYF).unwrap();
266        let glyph = font.outline_glyphs().get(GlyphId::new(2)).unwrap();
267        let mut outline = UnscaledOutlineBuf::<32>::new();
268        glyph
269            .draw_unscaled(LocationRef::default(), None, &mut outline)
270            .unwrap();
271        let outline = outline.as_ref();
272        let expected = [
273            // contour 0
274            (278, 710, 1),
275            (278, 470, 1),
276            (300, 500, 128),
277            (800, 500, 128),
278            (998, 470, 1),
279            (998, 710, 1),
280        ];
281        let points = outline
282            .points
283            .iter()
284            .map(|point| (point.x, point.y, point.flags.to_bits()))
285            .collect::<Vec<_>>();
286        assert_eq!(points, expected);
287    }
288
289    #[test]
290    fn read_cff_outline() {
291        let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
292        let glyph = font.outline_glyphs().get(GlyphId::new(2)).unwrap();
293        let mut outline = UnscaledOutlineBuf::<32>::new();
294        glyph
295            .draw_unscaled(LocationRef::default(), None, &mut outline)
296            .unwrap();
297        let outline = outline.as_ref();
298        let expected = [
299            // contour 0
300            (83, 0, 1),
301            (163, 0, 1),
302            (163, 482, 1),
303            (83, 482, 1),
304            // contour 1
305            (124, 595, 1),
306            (160, 595, 128),
307            (181, 616, 128),
308            (181, 652, 1),
309            (181, 688, 128),
310            (160, 709, 128),
311            (124, 709, 1),
312            (88, 709, 128),
313            (67, 688, 128),
314            (67, 652, 1),
315            (67, 616, 128),
316            (88, 595, 128),
317            (124, 595, 1),
318        ];
319        let points = outline
320            .points
321            .iter()
322            .map(|point| (point.x, point.y, point.flags.to_bits()))
323            .collect::<Vec<_>>();
324        assert_eq!(points, expected);
325    }
326
327    #[test]
328    fn find_vertical_extrema() {
329        let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
330        let glyph = font.outline_glyphs().get(GlyphId::new(5)).unwrap();
331        let mut outline = UnscaledOutlineBuf::<32>::new();
332        glyph
333            .draw_unscaled(LocationRef::default(), None, &mut outline)
334            .unwrap();
335        let outline = outline.as_ref();
336        // Find the maximum Y value and its containing contour
337        let mut top_y = None;
338        let (top_contour, top_point_ix) = outline
339            .find_last_contour(|point| {
340                if top_y.is_none() || Some(point.y) > top_y {
341                    top_y = Some(point.y);
342                    true
343                } else {
344                    false
345                }
346            })
347            .unwrap();
348        assert_eq!(top_contour, 14..26);
349        assert_eq!(top_point_ix, 5);
350        assert_eq!(top_y, Some(880));
351        // Find the minimum Y value and its containing contour
352        let mut bottom_y = None;
353        let (bottom_contour, bottom_point_ix) = outline
354            .find_last_contour(|point| {
355                if bottom_y.is_none() || Some(point.y) < bottom_y {
356                    bottom_y = Some(point.y);
357                    true
358                } else {
359                    false
360                }
361            })
362            .unwrap();
363        assert_eq!(bottom_contour, 0..14);
364        assert_eq!(bottom_point_ix, 0);
365        assert_eq!(bottom_y, Some(80));
366    }
367}