Skip to main content

write_fonts/tables/glyf/
simple.rs

1//! Simple glyphs (glyphs which do not contain components)
2
3use crate::{
4    from_obj::{FromObjRef, FromTableRef, ToOwnedTable},
5    util::{self, MultiZip, WrappingGet},
6    FontWrite, OtRound,
7};
8
9use kurbo::BezPath;
10use read_fonts::{tables::glyf::SimpleGlyphFlags, FontRead, ReadArgs};
11
12pub use read_fonts::tables::glyf::CurvePoint;
13
14use super::Bbox;
15
16/// A simple (without components) glyph
17#[derive(Clone, Debug, Default, PartialEq, Eq)]
18pub struct SimpleGlyph {
19    pub bbox: Bbox,
20    pub contours: Vec<Contour>,
21    pub instructions: Vec<u8>,
22    /// If set, the contours of the glyph overlap
23    ///
24    /// The OVERLAP_SIMPLE bit will be set on the first point of the first contour if this is true.
25    pub overlaps: bool,
26}
27
28/// A single contour comprising line, quadratic, or cubic bezier segments.
29#[derive(Clone, Debug, Default, PartialEq, Eq)]
30pub struct Contour(Vec<CurvePoint>);
31
32/// An error if an input curve is malformed
33#[derive(Clone, Debug)]
34#[non_exhaustive]
35pub enum MalformedPath {
36    HasCubic,
37    TooSmall,
38    MissingMove,
39    UnequalNumberOfElements(Vec<usize>),
40    InconsistentPathElements(usize, Vec<&'static str>),
41}
42
43impl SimpleGlyph {
44    /// Attempt to create a simple glyph from a kurbo `BezPath`
45    ///
46    /// The path may contain only line and quadratic bezier segments. The caller
47    /// is responsible for converting any cubic segments to quadratics before
48    /// calling.
49    ///
50    /// Returns an error if the input path is malformed; that is, if it is empty,
51    /// contains cubic segments, or does not begin with a 'move' instruction.
52    ///
53    /// **Context**
54    ///
55    /// * In the glyf table simple (contour based) glyph paths implicitly close when rendering.
56    /// * In font sources, and svg, open and closed paths are distinct.
57    ///    * In SVG closure matters due to influence on strokes, <https://www.w3.org/TR/SVG11/paths.html#PathDataClosePathCommand>.
58    /// * An explicit closePath joins the first/last points of a contour
59    ///    * This is not the same as ending with some other drawing command whose endpoint is the contour startpoint
60    /// * In FontTools endPath says I'm done with this subpath, [BezPath] has no endPath.
61    ///
62    /// Context courtesy of @anthrotype.
63    pub fn from_bezpath(path: &BezPath) -> Result<Self, MalformedPath> {
64        Self::interpolatable_glyphs_from_bezpaths(std::slice::from_ref(path))
65            .map(|mut x| x.pop().unwrap())
66    }
67
68    /// Attempt to create a set of interpolation-compatible glyphs from a set
69    /// of paths.
70    ///
71    /// The paths are expected to be preprocessed, and interpolation compatible
72    /// (i.e. they should have the same number and type of points, in the same
73    /// order.) They should contain only line and quadratic segments; the caller
74    /// is responsible for converting cubics to quadratics as needed.
75    ///
76    /// This method is provided for use when compiling variable fonts.
77    /// The inputs are expected to be different instances of the same named
78    /// glyph, each corresponding to a different location in the variation
79    /// space.
80    pub fn interpolatable_glyphs_from_bezpaths(
81        paths: &[BezPath],
82    ) -> Result<Vec<Self>, MalformedPath> {
83        simple_glyphs_from_kurbo(paths)
84    }
85
86    /// Compute the flags and deltas for this glyph's points.
87    ///
88    /// This does not do the final binary encoding, and it also does not handle
89    /// repeating flags, which doesn't really work when we're an iterator.
90    ///
91    // this is adapted from simon's implementation at
92    // https://github.com/simoncozens/rust-font-tools/blob/105436d3a617ddbebd25f790b041ff506bd90d44/fonttools-rs/src/tables/glyf/glyph.rs#L268
93    fn compute_point_deltas(
94        &self,
95    ) -> impl Iterator<Item = (SimpleGlyphFlags, CoordDelta, CoordDelta)> + '_ {
96        // reused for x & y by passing in the flags
97        fn flag_and_delta(
98            value: i16,
99            short_flag: SimpleGlyphFlags,
100            same_or_pos: SimpleGlyphFlags,
101        ) -> (SimpleGlyphFlags, CoordDelta) {
102            const SHORT_MAX: i16 = u8::MAX as i16;
103            const SHORT_MIN: i16 = -SHORT_MAX;
104            match value {
105                0 => (same_or_pos, CoordDelta::Skip),
106                SHORT_MIN..=-1 => (short_flag, CoordDelta::Short(value.unsigned_abs() as u8)),
107                1..=SHORT_MAX => (short_flag | same_or_pos, CoordDelta::Short(value as _)),
108                _other => (SimpleGlyphFlags::empty(), CoordDelta::Long(value)),
109            }
110        }
111
112        let (mut last_x, mut last_y) = (0, 0);
113        let mut iter = self.contours.iter().flat_map(|c| c.iter());
114        std::iter::from_fn(move || {
115            let point = iter.next()?;
116            let mut flag = SimpleGlyphFlags::empty();
117            let d_x = point.x - last_x;
118            let d_y = point.y - last_y;
119            last_x = point.x;
120            last_y = point.y;
121
122            if point.on_curve {
123                flag |= SimpleGlyphFlags::ON_CURVE_POINT;
124            }
125            if point.cubic {
126                flag |= SimpleGlyphFlags::CUBIC;
127            }
128            let (x_flag, x_data) = flag_and_delta(
129                d_x,
130                SimpleGlyphFlags::X_SHORT_VECTOR,
131                SimpleGlyphFlags::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
132            );
133            let (y_flag, y_data) = flag_and_delta(
134                d_y,
135                SimpleGlyphFlags::Y_SHORT_VECTOR,
136                SimpleGlyphFlags::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
137            );
138
139            flag |= x_flag | y_flag;
140            Some((flag, x_data, y_data))
141        })
142    }
143
144    /// Recompute the Glyph's bounding box based on the current contours
145    pub fn recompute_bounding_box(&mut self) {
146        let mut points = self
147            .contours
148            .iter()
149            .flat_map(|c| c.iter())
150            .map(|p| (p.x, p.y));
151
152        if let Some((mut x_min, mut y_min)) = points.next() {
153            let mut x_max = x_min;
154            let mut y_max = y_min;
155            for (x, y) in points {
156                x_min = x_min.min(x);
157                y_min = y_min.min(y);
158                x_max = x_max.max(x);
159                y_max = y_max.max(y);
160            }
161            self.bbox = Bbox {
162                x_min,
163                y_min,
164                x_max,
165                y_max,
166            };
167        }
168    }
169}
170
171impl Contour {
172    /// The total number of points in this contour
173    pub fn len(&self) -> usize {
174        self.0.len()
175    }
176
177    /// `true` if this contour is empty
178    pub fn is_empty(&self) -> bool {
179        self.0.is_empty()
180    }
181
182    pub fn iter(&self) -> impl Iterator<Item = &CurvePoint> {
183        self.0.iter()
184    }
185}
186
187impl From<Vec<CurvePoint>> for Contour {
188    fn from(points: Vec<CurvePoint>) -> Self {
189        Self(points)
190    }
191}
192
193impl From<Contour> for Vec<CurvePoint> {
194    fn from(contour: Contour) -> Self {
195        contour.0
196    }
197}
198
199impl MalformedPath {
200    fn inconsistent_path_els(idx: usize, elements: &[kurbo::PathEl]) -> Self {
201        fn el_types(elements: &[kurbo::PathEl]) -> Vec<&'static str> {
202            elements
203                .iter()
204                .map(|el| match el {
205                    kurbo::PathEl::MoveTo(_) => "M",
206                    kurbo::PathEl::LineTo(_) => "L",
207                    kurbo::PathEl::QuadTo(_, _) => "Q",
208                    kurbo::PathEl::CurveTo(_, _, _) => "C",
209                    kurbo::PathEl::ClosePath => "Z",
210                })
211                .collect()
212        }
213
214        MalformedPath::InconsistentPathElements(idx, el_types(elements))
215    }
216}
217
218/// A little helper for managing how we're representing a given delta
219#[derive(Clone, Copy, Debug)]
220enum CoordDelta {
221    // this is a repeat (set in the flag) and so we write nothing
222    Skip,
223    Short(u8),
224    Long(i16),
225}
226
227impl FontWrite for CoordDelta {
228    fn write_into(&self, writer: &mut crate::TableWriter) {
229        match self {
230            CoordDelta::Skip => (),
231            CoordDelta::Short(val) => val.write_into(writer),
232            CoordDelta::Long(val) => val.write_into(writer),
233        }
234    }
235}
236
237impl FromObjRef<read_fonts::tables::glyf::SimpleGlyph<'_>> for SimpleGlyph {
238    fn from_obj_ref(
239        from: &read_fonts::tables::glyf::SimpleGlyph,
240        _data: read_fonts::FontData,
241    ) -> Self {
242        let bbox = Bbox {
243            x_min: from.x_min(),
244            y_min: from.y_min(),
245            x_max: from.x_max(),
246            y_max: from.y_max(),
247        };
248        let mut points = from.points();
249        let mut last_end = 0;
250        let mut contours = vec![];
251        for end_pt in from.end_pts_of_contours() {
252            let end = end_pt.get() as usize + 1;
253            let count = end - last_end;
254            last_end = end;
255            contours.push(Contour(points.by_ref().take(count).collect()));
256        }
257        Self {
258            bbox,
259            contours,
260            instructions: from.instructions().to_owned(),
261            overlaps: from.has_overlapping_contours(),
262        }
263    }
264}
265
266impl FromTableRef<read_fonts::tables::glyf::SimpleGlyph<'_>> for SimpleGlyph {}
267
268impl ReadArgs for SimpleGlyph {
269    type Args = ();
270}
271
272impl<'a> FontRead<'a> for SimpleGlyph {
273    fn read_with_args(
274        data: read_fonts::FontData<'a>,
275        _: (),
276    ) -> Result<Self, read_fonts::ReadError> {
277        read_fonts::tables::glyf::SimpleGlyph::read(data).map(|g| g.to_owned_table())
278    }
279}
280
281impl FontWrite for SimpleGlyph {
282    fn write_into(&self, writer: &mut crate::TableWriter) {
283        assert!(self.contours.len() < i16::MAX as usize);
284        assert!(self.instructions.len() < u16::MAX as usize);
285        let n_contours = self.contours.len() as i16;
286        if n_contours == 0 {
287            // we don't bother writing empty glyphs
288            return;
289        }
290        n_contours.write_into(writer);
291        self.bbox.write_into(writer);
292        // now write end points of contours:
293        let mut cur = 0;
294        for contour in &self.contours {
295            cur += contour.len();
296            (cur as u16 - 1).write_into(writer);
297        }
298        (self.instructions.len() as u16).write_into(writer);
299        self.instructions.write_into(writer);
300
301        let mut deltas = self.compute_point_deltas().collect::<Vec<_>>();
302        if self.overlaps {
303            if let Some((flags, _, _)) = deltas.first_mut() {
304                flags.insert(SimpleGlyphFlags::OVERLAP_SIMPLE);
305            }
306        }
307        RepeatableFlag::iter_from_flags(deltas.iter().map(|(flag, _, _)| *flag))
308            .for_each(|flag| flag.write_into(writer));
309        deltas.iter().for_each(|(_, x, _)| x.write_into(writer));
310        deltas.iter().for_each(|(_, _, y)| y.write_into(writer));
311        writer.pad_to_2byte_aligned();
312    }
313}
314
315/// A little helper for writing flags that may have a 'repeat' byte
316#[derive(Clone, Copy, Debug, PartialEq, Eq)]
317struct RepeatableFlag {
318    flag: SimpleGlyphFlags,
319    repeat: u8,
320}
321
322impl FontWrite for RepeatableFlag {
323    fn write_into(&self, writer: &mut crate::TableWriter) {
324        debug_assert_eq!(
325            self.flag.contains(SimpleGlyphFlags::REPEAT_FLAG),
326            self.repeat > 0
327        );
328
329        self.flag.bits().write_into(writer);
330        if self.flag.contains(SimpleGlyphFlags::REPEAT_FLAG) {
331            self.repeat.write_into(writer);
332        }
333    }
334}
335
336impl RepeatableFlag {
337    /// given an iterator over raw flags, return an iterator over flags + repeat values
338    // writing this as an iterator instead of just returning a vec is very marginal
339    // gains, but I'm just in the habit at this point
340    fn iter_from_flags(
341        flags: impl IntoIterator<Item = SimpleGlyphFlags>,
342    ) -> impl Iterator<Item = RepeatableFlag> {
343        let mut iter = flags.into_iter();
344        let mut prev = None;
345        // if a flag repeats exactly once, then there is no (space) cost difference
346        // between 1) using a repeat flag followed by a value of '1' and 2) just
347        // repeating the flag (without setting the repeat bit).
348        // It would be simplest for us to go with option 1), but fontmake goes
349        // with 2). We like doing what fontmake does, so we add an extra step
350        // where if we see a case where there's a single repeat, we split it into
351        // two separate non-repeating flags.
352        let mut decompose_single_repeat = None;
353
354        std::iter::from_fn(move || loop {
355            if let Some(repeat) = decompose_single_repeat.take() {
356                return Some(repeat);
357            }
358
359            match (iter.next(), prev.take()) {
360                (None, Some(RepeatableFlag { flag, repeat: 1 })) => {
361                    let flag = flag & !SimpleGlyphFlags::REPEAT_FLAG;
362                    decompose_single_repeat = Some(RepeatableFlag { flag, repeat: 0 });
363                    return decompose_single_repeat;
364                }
365                (None, prev) => return prev,
366                (Some(flag), None) => prev = Some(RepeatableFlag { flag, repeat: 0 }),
367                (Some(flag), Some(mut last)) => {
368                    if (last.flag & !SimpleGlyphFlags::REPEAT_FLAG) == flag && last.repeat < u8::MAX
369                    {
370                        last.repeat += 1;
371                        last.flag |= SimpleGlyphFlags::REPEAT_FLAG;
372                        prev = Some(last);
373                    } else {
374                        // split a single repeat into two non-repeat flags
375                        if last.repeat == 1 {
376                            last.flag &= !SimpleGlyphFlags::REPEAT_FLAG;
377                            last.repeat = 0;
378                            // stash the extra flag, which we'll use at the top
379                            // of the next pass of the loop
380                            decompose_single_repeat = Some(last);
381                        }
382                        prev = Some(RepeatableFlag { flag, repeat: 0 });
383                        return Some(last);
384                    }
385                }
386            }
387        })
388    }
389}
390
391impl crate::validate::Validate for SimpleGlyph {
392    fn validate_impl(&self, ctx: &mut crate::codegen_prelude::ValidationCtx) {
393        if self.instructions.len() > u16::MAX as usize {
394            ctx.report("instructions len overflows");
395        }
396    }
397}
398
399/// Point with an associated on-curve flag.
400///
401/// Similar to read_fonts::tables::glyf::CurvePoint, but uses kurbo::Point directly
402/// thus it does not require (x, y) coordinates to be rounded to integers.
403#[derive(Clone, Copy, Debug, PartialEq)]
404struct ContourPoint {
405    point: kurbo::Point,
406    on_curve: bool,
407}
408
409impl ContourPoint {
410    fn new(point: kurbo::Point, on_curve: bool) -> Self {
411        Self { point, on_curve }
412    }
413
414    fn on_curve(point: kurbo::Point) -> Self {
415        Self::new(point, true)
416    }
417
418    fn off_curve(point: kurbo::Point) -> Self {
419        Self::new(point, false)
420    }
421}
422
423impl From<ContourPoint> for CurvePoint {
424    fn from(pt: ContourPoint) -> Self {
425        let (x, y) = pt.point.ot_round();
426        CurvePoint::new(x, y, pt.on_curve)
427    }
428}
429/// A helper struct for building interpolatable contours
430///
431/// Holds a vec of contour, one contour per glyph.
432#[derive(Clone, Debug, PartialEq)]
433struct InterpolatableContourBuilder(Vec<Vec<ContourPoint>>);
434
435impl InterpolatableContourBuilder {
436    /// Create new set of interpolatable contours beginning at the provided points
437    fn new(move_pts: &[kurbo::Point]) -> Self {
438        assert!(!move_pts.is_empty());
439        Self(
440            move_pts
441                .iter()
442                .map(|pt| vec![ContourPoint::on_curve(*pt)])
443                .collect(),
444        )
445    }
446
447    /// Number of interpolatable contours (one per glyph)
448    fn len(&self) -> usize {
449        self.0.len()
450    }
451
452    /// Add a line segment to all contours
453    fn line_to(&mut self, pts: &[kurbo::Point]) {
454        assert_eq!(pts.len(), self.len());
455        for (i, pt) in pts.iter().enumerate() {
456            self.0[i].push(ContourPoint::on_curve(*pt));
457        }
458    }
459
460    /// Add a quadratic curve segment to all contours
461    fn quad_to(&mut self, pts: &[(kurbo::Point, kurbo::Point)]) {
462        for (i, (p0, p1)) in pts.iter().enumerate() {
463            self.0[i].push(ContourPoint::off_curve(*p0));
464            self.0[i].push(ContourPoint::on_curve(*p1));
465        }
466    }
467
468    /// The total number of points in each interpolatable contour
469    fn num_points(&self) -> usize {
470        let n = self.0[0].len();
471        assert!(self.0.iter().all(|c| c.len() == n));
472        n
473    }
474
475    /// The first point in each contour
476    fn first(&self) -> impl Iterator<Item = &ContourPoint> {
477        self.0.iter().map(|v| v.first().unwrap())
478    }
479
480    /// The last point in each contour
481    fn last(&self) -> impl Iterator<Item = &ContourPoint> {
482        self.0.iter().map(|v| v.last().unwrap())
483    }
484
485    /// Remove the last point from each contour
486    fn remove_last(&mut self) {
487        self.0.iter_mut().for_each(|c| {
488            c.pop().unwrap();
489        });
490    }
491
492    fn is_implicit_on_curve(&self, idx: usize) -> bool {
493        self.0
494            .iter()
495            .all(|points| is_implicit_on_curve(points, idx))
496    }
497
498    /// Build the contours, dropping any on-curve points that can be implied in all contours
499    fn build(self) -> Vec<Contour> {
500        let num_contours = self.len();
501        let num_points = self.num_points();
502        let mut contours = vec![Contour::default(); num_contours];
503        contours.iter_mut().for_each(|c| c.0.reserve(num_points));
504        for point_idx in (0..num_points).filter(|point_idx| !self.is_implicit_on_curve(*point_idx))
505        {
506            for (contour_idx, contour) in contours.iter_mut().enumerate() {
507                contour
508                    .0
509                    .push(CurvePoint::from(self.0[contour_idx][point_idx]));
510            }
511        }
512        contours
513    }
514}
515
516/// True if p1 is the midpoint of p0 and p2.
517///
518/// We check both before and after rounding float coordinates to integer to avoid
519/// false negatives due to rounding.
520#[inline]
521fn is_mid_point(p0: kurbo::Point, p1: kurbo::Point, p2: kurbo::Point) -> bool {
522    let mid = p0.midpoint(p2);
523    (util::isclose(mid.x, p1.x) && util::isclose(mid.y, p1.y))
524        || p0.to_vec2().ot_round() + p2.to_vec2().ot_round() == p1.to_vec2().ot_round() * 2.0
525}
526
527fn is_implicit_on_curve(points: &[ContourPoint], idx: usize) -> bool {
528    let p1 = &points[idx]; // user error if this is out of bounds
529    if !p1.on_curve {
530        return false;
531    }
532    let p0 = points.wrapping_prev(idx);
533    let p2 = points.wrapping_next(idx);
534    if p0.on_curve || p0.on_curve != p2.on_curve {
535        return false;
536    }
537    // drop p1 if halfway between p0 and p2
538    is_mid_point(p0.point, p1.point, p2.point)
539}
540
541// impl for SimpleGlyph::interpolatable_glyphs_from_paths
542fn simple_glyphs_from_kurbo(paths: &[BezPath]) -> Result<Vec<SimpleGlyph>, MalformedPath> {
543    // check that all paths have the same number of elements so we can zip them together
544    let num_elements: Vec<usize> = paths.iter().map(|path| path.elements().len()).collect();
545    if num_elements.iter().any(|n| *n != num_elements[0]) {
546        return Err(MalformedPath::UnequalNumberOfElements(num_elements));
547    }
548    let path_iters = MultiZip::new(paths.iter().map(|path| path.iter()).collect());
549    let mut contours: Vec<InterpolatableContourBuilder> = Vec::new();
550    let mut current: Option<InterpolatableContourBuilder> = None;
551    let num_glyphs = paths.len();
552    let mut pts = Vec::with_capacity(num_glyphs);
553    let mut quad_pts = Vec::with_capacity(num_glyphs);
554    for (i, elements) in path_iters.enumerate() {
555        // All i-th path elements are expected to have the same types.
556        // elements is never empty (if it were, MultiZip would have stopped), hence the unwrap
557        let first_el = elements.first().unwrap();
558        match first_el {
559            kurbo::PathEl::MoveTo(_) => {
560                // we have a new contour, flush the current one
561                if let Some(prev) = current.take() {
562                    contours.push(prev);
563                }
564                pts.clear();
565                for el in &elements {
566                    match el {
567                        &kurbo::PathEl::MoveTo(pt) => {
568                            pts.push(pt);
569                        }
570                        _ => return Err(MalformedPath::inconsistent_path_els(i, &elements)),
571                    }
572                }
573                current = Some(InterpolatableContourBuilder::new(&pts));
574            }
575            kurbo::PathEl::LineTo(_) => {
576                pts.clear();
577                for el in &elements {
578                    match el {
579                        &kurbo::PathEl::LineTo(pt) => {
580                            pts.push(pt);
581                        }
582                        _ => return Err(MalformedPath::inconsistent_path_els(i, &elements)),
583                    }
584                }
585                current
586                    .as_mut()
587                    .ok_or(MalformedPath::MissingMove)?
588                    .line_to(&pts)
589            }
590            kurbo::PathEl::QuadTo(_, _) => {
591                quad_pts.clear();
592                for el in &elements {
593                    match el {
594                        &kurbo::PathEl::QuadTo(p0, p1) => {
595                            quad_pts.push((p0, p1));
596                        }
597                        _ => return Err(MalformedPath::inconsistent_path_els(i, &elements)),
598                    }
599                }
600                current
601                    .as_mut()
602                    .ok_or(MalformedPath::MissingMove)?
603                    .quad_to(&quad_pts)
604            }
605            kurbo::PathEl::CurveTo(_, _, _) => return Err(MalformedPath::HasCubic),
606            kurbo::PathEl::ClosePath => {
607                let contour = current.as_mut().ok_or(MalformedPath::MissingMove)?;
608                // remove last point in closed path if has same coords as the move point
609                // matches FontTools handling @ https://github.com/fonttools/fonttools/blob/3b9a73ff8379ab49d3ce35aaaaf04b3a7d9d1655/Lib/fontTools/pens/pointPen.py#L321-L323
610                // FontTools has an else case to support UFO glif's choice to not include 'move' for closed paths that does not apply here.
611                if contour.num_points() > 1 && contour.last().eq(contour.first()) {
612                    contour.remove_last();
613                }
614            }
615        }
616    }
617    contours.extend(current);
618
619    let mut glyph_contours = vec![Vec::new(); num_glyphs];
620    for builder in contours {
621        assert_eq!(builder.len(), num_glyphs);
622        for (i, contour) in builder.build().into_iter().enumerate() {
623            glyph_contours[i].push(contour);
624        }
625    }
626
627    let mut glyphs = Vec::new();
628    for (contours, path) in glyph_contours.into_iter().zip(paths.iter()) {
629        // https://github.com/googlefonts/fontmake-rs/issues/285 we want control point box, not tight bbox
630        // so don't call path.bounding_box
631        glyphs.push(SimpleGlyph {
632            bbox: path.control_box().into(),
633            contours,
634            instructions: Default::default(),
635            overlaps: false,
636        })
637    }
638
639    Ok(glyphs)
640}
641
642#[cfg(test)]
643mod tests {
644    use font_types::GlyphId;
645    use kurbo::Affine;
646    use read_fonts::{tables::glyf as read_glyf, FontRef, TableProvider};
647
648    use super::*;
649
650    #[test]
651    fn roundtrip_cubic_flags_in_extended_simple_glyph() {
652        let bytes = font_test_data::extended::simple_glyph(&[
653            (0, 0, 0x81),
654            (20, 80, 0x80),
655            (60, 80, 0x80),
656            (80, 0, 0x81),
657        ]);
658        let read = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
659        let points: Vec<_> = read.points().collect();
660        assert!(points.iter().all(|point| point.cubic));
661        assert!(points[0].on_curve);
662        assert!(!points[1].on_curve);
663        let owned: SimpleGlyph = read.to_owned_table();
664        let compiled = crate::dump_table(&owned).unwrap();
665        let read = read_glyf::SimpleGlyph::read(compiled.as_slice().into()).unwrap();
666        assert_eq!(read.points().collect::<Vec<_>>(), points);
667        let mut fast_points = vec![read_fonts::types::Point::<i32>::default(); points.len()];
668        let mut flags = vec![read_glyf::PointFlags::default(); points.len()];
669        read.read_points_fast(&mut fast_points, &mut flags).unwrap();
670        assert_eq!(
671            flags.iter().map(|flag| flag.to_bits()).collect::<Vec<_>>(),
672            [0x81, 0x80, 0x80, 0x81]
673        );
674    }
675
676    // For `indexToLocFormat == 0` (short version), offset divided by 2 is stored, so add a padding
677    // byte if the length is not even to ensure our computed bytes match those of our test glyphs.
678    fn pad_for_loca_format(loca: &read_fonts::tables::loca::Loca, mut bytes: Vec<u8>) -> Vec<u8> {
679        if matches!(loca, read_fonts::tables::loca::Loca::Short(_)) && bytes.len() & 1 != 0 {
680            bytes.push(0);
681        }
682        bytes
683    }
684
685    #[test]
686    fn bad_path_input() {
687        let mut path = BezPath::new();
688        path.move_to((0., 0.));
689        path.curve_to((10., 10.), (20., 20.), (30., 30.));
690        path.line_to((50., 50.));
691        path.line_to((10., 10.));
692        let err = SimpleGlyph::from_bezpath(&path).unwrap_err();
693        assert!(matches!(err, MalformedPath::HasCubic));
694    }
695
696    #[test]
697    fn read_write_simple() {
698        let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
699        let loca = font.loca(None).unwrap();
700        let glyf = font.glyf().unwrap();
701        let read_glyf::Glyph::Simple(orig) = loca
702            .get(GlyphId::new(0), &glyf)
703            .and_then(|g| g.into_glyph())
704            .unwrap()
705        else {
706            panic!("not a simple glyph")
707        };
708        let orig_bytes = orig.offset_data();
709
710        let ours = SimpleGlyph::from_table_ref(&orig);
711        let bytes = pad_for_loca_format(&loca, crate::dump_table(&ours).unwrap());
712        let ours = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
713
714        let our_points = ours.points().collect::<Vec<_>>();
715        let their_points = orig.points().collect::<Vec<_>>();
716        assert_eq!(our_points, their_points);
717        assert_eq!(orig_bytes.as_ref(), bytes);
718        assert_eq!(orig.glyph_data(), ours.glyph_data());
719        assert_eq!(orig_bytes.len(), bytes.len());
720    }
721
722    #[test]
723    fn round_trip_simple() {
724        let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
725        let loca = font.loca(None).unwrap();
726        let glyf = font.glyf().unwrap();
727        let read_glyf::Glyph::Simple(orig) = loca
728            .get(GlyphId::new(2), &glyf)
729            .and_then(|g| g.into_glyph())
730            .unwrap()
731        else {
732            panic!("not a simple glyph")
733        };
734        let orig_bytes = orig.offset_data();
735
736        let bezpath = BezPath::from_svg("M278,710 L278,470 L998,470 L998,710 Z").unwrap();
737
738        let ours = SimpleGlyph::from_bezpath(&bezpath).unwrap();
739        let bytes = pad_for_loca_format(&loca, crate::dump_table(&ours).unwrap());
740        let ours = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
741
742        let our_points = ours.points().collect::<Vec<_>>();
743        let their_points = orig.points().collect::<Vec<_>>();
744        assert_eq!(our_points, their_points);
745        assert_eq!(orig_bytes.as_ref(), bytes);
746        assert_eq!(orig.glyph_data(), ours.glyph_data());
747        assert_eq!(orig_bytes.len(), bytes.len());
748    }
749
750    #[test]
751    fn round_trip_multi_contour() {
752        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
753        let loca = font.loca(None).unwrap();
754        let glyf = font.glyf().unwrap();
755        let read_glyf::Glyph::Simple(orig) = loca
756            .get(GlyphId::new(1), &glyf)
757            .and_then(|g| g.into_glyph())
758            .unwrap()
759        else {
760            panic!("not a simple glyph")
761        };
762        let orig_bytes = orig.offset_data();
763
764        let bezpath = BezPath::from_svg("M708,1327 L226,0 L29,0 L584,1456 L711,1456 Z M1112,0 L629,1327 L626,1456 L753,1456 L1310,0 Z M1087,539 L1087,381 L269,381 L269,539 Z").unwrap();
765
766        let ours = SimpleGlyph::from_bezpath(&bezpath).unwrap();
767        let bytes = pad_for_loca_format(&loca, crate::dump_table(&ours).unwrap());
768        let ours = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
769
770        let our_points = ours.points().collect::<Vec<_>>();
771        let their_points = orig.points().collect::<Vec<_>>();
772        dbg!(
773            SimpleGlyphFlags::from_bits(1),
774            SimpleGlyphFlags::from_bits(9)
775        );
776        assert_eq!(our_points, their_points);
777        assert_eq!(orig.glyph_data(), ours.glyph_data());
778        assert_eq!(orig_bytes.len(), bytes.len());
779        assert_eq!(orig_bytes.as_ref(), bytes);
780    }
781
782    #[test]
783    fn simple_glyph_open_path() {
784        let mut path = BezPath::new();
785        path.move_to((20., -100.));
786        path.quad_to((1337., 1338.), (-50., -69.0));
787        path.quad_to((13., 255.), (-255., 256.));
788        // even if the last point is on top of the first, the path was not deliberately closed
789        // hence there is going to be an extra point (6, not 5 in total)
790        path.line_to((20., -100.));
791
792        let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
793        let bytes = crate::dump_table(&glyph).unwrap();
794        let read = read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
795        assert_eq!(read.number_of_contours(), 1);
796        assert_eq!(read.num_points(), 6);
797        assert_eq!(read.end_pts_of_contours(), &[5]);
798        let points = read.points().collect::<Vec<_>>();
799        assert_eq!(points[0].x, 20);
800        assert_eq!(points[0].y, -100);
801        assert!(points[0].on_curve);
802        assert_eq!(points[1].x, 1337);
803        assert_eq!(points[1].y, 1338);
804        assert!(!points[1].on_curve);
805        assert_eq!(points[4].x, -255);
806        assert_eq!(points[4].y, 256);
807        assert!(points[4].on_curve);
808        assert_eq!(points[5].x, 20);
809        assert_eq!(points[5].y, -100);
810        assert!(points[5].on_curve);
811    }
812
813    #[test]
814    fn simple_glyph_closed_path_implicit_vs_explicit_closing_line() {
815        let mut path1 = BezPath::new();
816        path1.move_to((20., -100.));
817        path1.quad_to((1337., 1338.), (-50., -69.0));
818        path1.quad_to((13., 255.), (-255., 256.));
819        path1.close_path();
820
821        let mut path2 = BezPath::new();
822        path2.move_to((20., -100.));
823        path2.quad_to((1337., 1338.), (-50., -69.0));
824        path2.quad_to((13., 255.), (-255., 256.));
825        // this line_to (absent from path1) makes no difference since in both cases the
826        // path is closed with a close_path (5 points in total, not 6)
827        path2.line_to((20., -100.));
828        path2.close_path();
829
830        for path in &[path1, path2] {
831            let glyph = SimpleGlyph::from_bezpath(path).unwrap();
832            let bytes = crate::dump_table(&glyph).unwrap();
833            let read =
834                read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
835            assert_eq!(read.number_of_contours(), 1);
836            assert_eq!(read.num_points(), 5);
837            assert_eq!(read.end_pts_of_contours(), &[4]);
838            let points = read.points().collect::<Vec<_>>();
839            assert_eq!(points[0].x, 20);
840            assert_eq!(points[0].y, -100);
841            assert!(points[0].on_curve);
842            assert_eq!(points[1].x, 1337);
843            assert_eq!(points[1].y, 1338);
844            assert!(!points[1].on_curve);
845            assert_eq!(points[4].x, -255);
846            assert_eq!(points[4].y, 256);
847            assert!(points[4].on_curve);
848        }
849    }
850
851    #[test]
852    fn keep_single_point_contours() {
853        // single points may be meaningless, but are also harmless
854        let mut path = BezPath::new();
855        path.move_to((0.0, 0.0));
856        // path.close_path();  // doesn't really matter if this is closed
857        path.move_to((1.0, 2.0));
858        path.close_path();
859
860        let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
861        let bytes = crate::dump_table(&glyph).unwrap();
862        let read = read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
863        assert_eq!(read.number_of_contours(), 2);
864        assert_eq!(read.num_points(), 2);
865        assert_eq!(read.end_pts_of_contours(), &[0, 1]);
866        let points = read.points().collect::<Vec<_>>();
867        assert_eq!(points[0].x, 0);
868        assert_eq!(points[0].y, 0);
869        assert!(points[0].on_curve);
870        assert_eq!(points[1].x, 1);
871        assert_eq!(points[1].y, 2);
872        assert!(points[0].on_curve);
873    }
874
875    #[test]
876    fn compile_repeatable_flags() {
877        let mut path = BezPath::new();
878        path.move_to((20., -100.));
879        path.line_to((25., -90.));
880        path.line_to((50., -69.));
881        path.line_to((80., -20.));
882
883        let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
884        let flags = glyph
885            .compute_point_deltas()
886            .map(|x| x.0)
887            .collect::<Vec<_>>();
888        let r_flags = RepeatableFlag::iter_from_flags(flags.iter().copied()).collect::<Vec<_>>();
889
890        assert_eq!(r_flags.len(), 2, "{r_flags:?}");
891        let bytes = crate::dump_table(&glyph).unwrap();
892        let read = read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
893        assert_eq!(read.number_of_contours(), 1);
894        assert_eq!(read.num_points(), 4);
895        assert_eq!(read.end_pts_of_contours(), &[3]);
896        let points = read.points().collect::<Vec<_>>();
897        assert_eq!(points[0].x, 20);
898        assert_eq!(points[0].y, -100);
899        assert_eq!(points[1].x, 25);
900        assert_eq!(points[1].y, -90);
901        assert_eq!(points[2].x, 50);
902        assert_eq!(points[2].y, -69);
903        assert_eq!(points[3].x, 80);
904        assert_eq!(points[3].y, -20);
905    }
906
907    #[test]
908    fn simple_glyphs_from_kurbo_unequal_number_of_elements() {
909        let mut path1 = BezPath::new();
910        path1.move_to((0., 0.));
911        path1.line_to((1., 1.));
912        path1.line_to((2., 2.));
913        path1.line_to((0., 0.));
914        path1.close_path();
915        assert_eq!(path1.elements().len(), 5);
916
917        let mut path2 = BezPath::new();
918        path2.move_to((3., 3.));
919        path2.line_to((4., 4.));
920        path2.line_to((5., 5.));
921        path2.line_to((6., 6.));
922        path2.line_to((3., 3.));
923        path2.close_path();
924        assert_eq!(path2.elements().len(), 6);
925
926        let err = simple_glyphs_from_kurbo(&[path1, path2]).unwrap_err();
927        assert!(matches!(err, MalformedPath::UnequalNumberOfElements(_)));
928        assert_eq!(format!("{:?}", err), "UnequalNumberOfElements([5, 6])");
929    }
930
931    #[test]
932    fn simple_glyphs_from_kurbo_inconsistent_path_elements() {
933        let mut path1 = BezPath::new();
934        path1.move_to((0., 0.));
935        path1.line_to((1., 1.));
936        path1.quad_to((2., 2.), (0., 0.));
937        path1.close_path();
938        let mut path2 = BezPath::new();
939        path2.move_to((3., 3.));
940        path2.quad_to((4., 4.), (5., 5.)); // elements at index 1 are inconsistent
941        path2.line_to((3., 3.));
942        path2.close_path();
943
944        let err = simple_glyphs_from_kurbo(&[path1, path2]).unwrap_err();
945        assert!(matches!(err, MalformedPath::InconsistentPathElements(1, _)));
946        assert_eq!(
947            format!("{:?}", err),
948            "InconsistentPathElements(1, [\"L\", \"Q\"])"
949        );
950    }
951
952    /// Create a number of interpolatable BezPaths with the given element types.
953    /// The paths will be identical except for the point coordinates of the elements,
954    /// which will be (0.0, 0.0), (1.0, 1.0), (2.0, 2.0), etc. for each subsequent
955    /// element. If `last_pt_equal_move` is true, the last point of each sub-path
956    /// will be equal to the first (M) point of that sub-path.
957    /// E.g.:
958    /// ```
959    /// let paths = make_interpolatable_paths(2, "MLLZ", false);
960    /// println!("{:?}", paths[0].to_svg());
961    /// // "M0,0 L1,1 L2,2 Z"
962    /// println!("{:?}", paths[1].to_svg());
963    /// // "M3,3 L4,4 L5,5 Z"
964    /// let paths = make_interpolatable_paths(3, "MLLLZMQQZ", true);
965    /// println!("{:?}", paths[0].to_svg());
966    /// // "M0,0 L1,1 L2,2 L0,0 Z M3,3 Q4,4 5,5 Q6,6 3,3 Z"
967    /// println!("{:?}", paths[1].to_svg());
968    /// // "M7,7 L8,8 L9,9 L7,7 Z M10,10 Q11,11 12,12 Q13,13 10,10 Z"
969    /// println!("{:?}", paths[2].to_svg());
970    /// // "M14,14 L15,15 L16,16 L14,14 Z M17,17 Q18,18 19,19 Q20,20 17,17 Z"
971    /// ```
972    fn make_interpolatable_paths(
973        num_paths: usize,
974        el_types: &str,
975        last_pt_equal_move: bool,
976    ) -> Vec<BezPath> {
977        let mut paths = Vec::new();
978        // we don't care about the actual coordinate values, just use a counter
979        // that yields 0.0, 1.0, 2.0, 3.0, etc.
980        let mut start = 0.0;
981        let mut points = std::iter::from_fn(move || {
982            let value = start;
983            start += 1.0;
984            Some((value, value))
985        });
986        let el_types = el_types.chars().collect::<Vec<_>>();
987        assert!(!el_types.is_empty());
988        for _ in 0..num_paths {
989            let mut path = BezPath::new();
990            let mut start_pt = None;
991            // use peekable iterator so we can look ahead to next el_type
992            let mut el_types_iter = el_types.iter().peekable();
993            while let Some(&el_type) = el_types_iter.next() {
994                let next_el_type = el_types_iter.peek().map(|x| **x).unwrap_or('M');
995                match el_type {
996                    'M' => {
997                        start_pt = points.next();
998                        path.move_to(start_pt.unwrap());
999                    }
1000                    'L' => {
1001                        if matches!(next_el_type, 'Z' | 'M') && last_pt_equal_move {
1002                            path.line_to(start_pt.unwrap());
1003                        } else {
1004                            path.line_to(points.next().unwrap());
1005                        }
1006                    }
1007                    'Q' => {
1008                        let p1 = points.next().unwrap();
1009                        let p2 = if matches!(next_el_type, 'Z' | 'M') && last_pt_equal_move {
1010                            start_pt.unwrap()
1011                        } else {
1012                            points.next().unwrap()
1013                        };
1014                        path.quad_to(p1, p2);
1015                    }
1016                    'Z' => {
1017                        path.close_path();
1018                        start_pt = None;
1019                    }
1020                    _ => panic!("Unsupported element type {:?}", el_type),
1021                }
1022            }
1023            paths.push(path);
1024        }
1025        assert_eq!(paths.len(), num_paths);
1026        paths
1027    }
1028
1029    fn assert_contour_points(glyph: &SimpleGlyph, all_points: Vec<Vec<CurvePoint>>) {
1030        let expected_num_contours = all_points.len();
1031        assert_eq!(glyph.contours.len(), expected_num_contours);
1032        for (contour, expected_points) in glyph.contours.iter().zip(all_points.iter()) {
1033            let points = contour.iter().copied().collect::<Vec<_>>();
1034            assert_eq!(points, *expected_points);
1035        }
1036    }
1037
1038    #[test]
1039    fn simple_glyphs_from_kurbo_3_lines_closed() {
1040        // two triangles, each with 3 lines, explicitly closed
1041        let paths = make_interpolatable_paths(2, "MLLLZ", true);
1042        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1043
1044        assert_contour_points(
1045            &glyphs[0],
1046            vec![vec![
1047                CurvePoint::on_curve(0, 0),
1048                CurvePoint::on_curve(1, 1),
1049                CurvePoint::on_curve(2, 2),
1050            ]],
1051        );
1052        assert_contour_points(
1053            &glyphs[1],
1054            vec![vec![
1055                CurvePoint::on_curve(3, 3),
1056                CurvePoint::on_curve(4, 4),
1057                CurvePoint::on_curve(5, 5),
1058            ]],
1059        );
1060    }
1061
1062    #[test]
1063    fn simple_glyphs_from_kurbo_3_lines_implicitly_closed() {
1064        // two triangles, each with 2 lines plus the last implicit closing line
1065        let paths = make_interpolatable_paths(2, "MLLZ", false);
1066        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1067
1068        assert_contour_points(
1069            &glyphs[0],
1070            vec![vec![
1071                CurvePoint::on_curve(0, 0),
1072                CurvePoint::on_curve(1, 1),
1073                CurvePoint::on_curve(2, 2),
1074            ]],
1075        );
1076        assert_contour_points(
1077            &glyphs[1],
1078            vec![vec![
1079                CurvePoint::on_curve(3, 3),
1080                CurvePoint::on_curve(4, 4),
1081                CurvePoint::on_curve(5, 5),
1082            ]],
1083        );
1084    }
1085
1086    #[test]
1087    fn simple_glyphs_from_kurbo_2_quads_closed() {
1088        // two compatible paths each containing 2 consecutive quadratic bezier curves,
1089        // where the respective off-curves are placed at equal distance from the on-curve
1090        // point joining them; the paths are closed and the last quad point is the same
1091        // as the move point.
1092        let paths = make_interpolatable_paths(2, "MQQZ", true);
1093        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1094
1095        assert_contour_points(
1096            &glyphs[0],
1097            vec![vec![
1098                CurvePoint::on_curve(0, 0),
1099                CurvePoint::off_curve(1, 1),
1100                // CurvePoint::on_curve(2, 2),  // implied oncurve point dropped
1101                CurvePoint::off_curve(3, 3),
1102            ]],
1103        );
1104        assert_contour_points(
1105            &glyphs[1],
1106            vec![vec![
1107                CurvePoint::on_curve(4, 4),
1108                CurvePoint::off_curve(5, 5),
1109                // CurvePoint::on_curve(6, 6),  // implied
1110                CurvePoint::off_curve(7, 7),
1111            ]],
1112        );
1113    }
1114
1115    #[test]
1116    fn simple_glyphs_from_kurbo_2_quads_1_line_implicitly_closed() {
1117        // same path elements as above 'MQQZ' but with the last_pt_equal_move=false
1118        // thus this actually contains three segments: 2 quads plus the last implied
1119        // closing line. There is an additional on-curve point at the end of the path.
1120        let paths = make_interpolatable_paths(2, "MQQZ", false);
1121        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1122
1123        assert_contour_points(
1124            &glyphs[0],
1125            vec![vec![
1126                CurvePoint::on_curve(0, 0),
1127                CurvePoint::off_curve(1, 1),
1128                // CurvePoint::on_curve(2, 2),
1129                CurvePoint::off_curve(3, 3),
1130                CurvePoint::on_curve(4, 4),
1131            ]],
1132        );
1133        assert_contour_points(
1134            &glyphs[1],
1135            vec![vec![
1136                CurvePoint::on_curve(5, 5),
1137                CurvePoint::off_curve(6, 6),
1138                // CurvePoint::on_curve(7, 7),
1139                CurvePoint::off_curve(8, 8),
1140                CurvePoint::on_curve(9, 9),
1141            ]],
1142        );
1143    }
1144
1145    #[test]
1146    fn simple_glyphs_from_kurbo_multiple_contours_mixed_segments() {
1147        // four paths, each containing two sub-paths, with a mix of line and quad segments
1148        let paths = make_interpolatable_paths(4, "MLQQZMQLQLZ", true);
1149        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1150
1151        assert_contour_points(
1152            &glyphs[0],
1153            vec![
1154                vec![
1155                    CurvePoint::on_curve(0, 0),
1156                    CurvePoint::on_curve(1, 1),
1157                    CurvePoint::off_curve(2, 2),
1158                    // CurvePoint::on_curve(3, 3),
1159                    CurvePoint::off_curve(4, 4),
1160                ],
1161                vec![
1162                    CurvePoint::on_curve(5, 5),
1163                    CurvePoint::off_curve(6, 6),
1164                    CurvePoint::on_curve(7, 7),
1165                    CurvePoint::on_curve(8, 8),
1166                    CurvePoint::off_curve(9, 9),
1167                    CurvePoint::on_curve(10, 10),
1168                ],
1169            ],
1170        );
1171    }
1172
1173    #[test]
1174    fn simple_glyphs_from_kurbo_all_quad_off_curves() {
1175        // the following path contains only quadratic curves and all the on-curve points
1176        // can be implied, thus the resulting glyf contours contain only off-curves.
1177        let mut path1 = BezPath::new();
1178        path1.move_to((0.0, 1.0));
1179        path1.quad_to((1.0, 1.0), (1.0, 0.0));
1180        path1.quad_to((1.0, -1.0), (0.0, -1.0));
1181        path1.quad_to((-1.0, -1.0), (-1.0, 0.0));
1182        path1.quad_to((-1.0, 1.0), (0.0, 1.0));
1183        path1.close_path();
1184
1185        let mut path2 = path1.clone();
1186        path2.apply_affine(Affine::scale(2.0));
1187
1188        let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
1189
1190        assert_contour_points(
1191            &glyphs[0],
1192            vec![vec![
1193                CurvePoint::off_curve(1, 1),
1194                CurvePoint::off_curve(1, -1),
1195                CurvePoint::off_curve(-1, -1),
1196                CurvePoint::off_curve(-1, 1),
1197            ]],
1198        );
1199        assert_contour_points(
1200            &glyphs[1],
1201            vec![vec![
1202                CurvePoint::off_curve(2, 2),
1203                CurvePoint::off_curve(2, -2),
1204                CurvePoint::off_curve(-2, -2),
1205                CurvePoint::off_curve(-2, 2),
1206            ]],
1207        );
1208    }
1209
1210    #[test]
1211    fn simple_glyphs_from_kurbo_keep_on_curve_unless_impliable_for_all() {
1212        let mut path1 = BezPath::new();
1213        path1.move_to((0.0, 0.0));
1214        path1.quad_to((0.0, 1.0), (1.0, 1.0)); // on-curve equidistant from prev/next off-curves
1215        path1.quad_to((2.0, 1.0), (2.0, 0.0));
1216        path1.line_to((0.0, 0.0));
1217        path1.close_path();
1218
1219        // when making a SimpleGlyph from this path alone, the on-curve point at (1, 1)
1220        // can be implied/dropped.
1221        assert_contour_points(
1222            &SimpleGlyph::from_bezpath(&path1).unwrap(),
1223            vec![vec![
1224                CurvePoint::on_curve(0, 0),
1225                CurvePoint::off_curve(0, 1),
1226                // CurvePoint::on_curve(1, 1),  // implied
1227                CurvePoint::off_curve(2, 1),
1228                CurvePoint::on_curve(2, 0),
1229            ]],
1230        );
1231
1232        let mut path2 = BezPath::new();
1233        path2.move_to((0.0, 0.0));
1234        path2.quad_to((0.0, 2.0), (2.0, 2.0)); // on-curve NOT equidistant from prev/next off-curves
1235        path2.quad_to((3.0, 2.0), (3.0, 0.0));
1236        path2.line_to((0.0, 0.0));
1237        path2.close_path();
1238
1239        let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
1240
1241        // However, when making interpolatable SimpleGlyphs from both paths, the on-curve
1242        // can no longer be implied/dropped (for it is not impliable in the second path).
1243        assert_contour_points(
1244            &glyphs[0],
1245            vec![vec![
1246                CurvePoint::on_curve(0, 0),
1247                CurvePoint::off_curve(0, 1),
1248                CurvePoint::on_curve(1, 1), // NOT implied
1249                CurvePoint::off_curve(2, 1),
1250                CurvePoint::on_curve(2, 0),
1251            ]],
1252        );
1253        assert_contour_points(
1254            &glyphs[1],
1255            vec![vec![
1256                CurvePoint::on_curve(0, 0),
1257                CurvePoint::off_curve(0, 2),
1258                CurvePoint::on_curve(2, 2), // NOT implied
1259                CurvePoint::off_curve(3, 2),
1260                CurvePoint::on_curve(3, 0),
1261            ]],
1262        );
1263    }
1264
1265    #[test]
1266    fn simple_glyphs_from_kurbo_2_lines_open() {
1267        // these contours contain two lines each and are not closed (no 'Z'); they still
1268        // produce three points and are treated as closed for the sake of TrueType glyf.
1269        let paths = make_interpolatable_paths(2, "MLL", false);
1270        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1271
1272        assert_contour_points(
1273            &glyphs[0],
1274            vec![vec![
1275                CurvePoint::on_curve(0, 0),
1276                CurvePoint::on_curve(1, 1),
1277                CurvePoint::on_curve(2, 2),
1278            ]],
1279        );
1280        assert_contour_points(
1281            &glyphs[1],
1282            vec![vec![
1283                CurvePoint::on_curve(3, 3),
1284                CurvePoint::on_curve(4, 4),
1285                CurvePoint::on_curve(5, 5),
1286            ]],
1287        );
1288    }
1289
1290    #[test]
1291    fn simple_glyphs_from_kurbo_3_lines_open_duplicate_last_pt() {
1292        // two paths with one open contour, containing three line segments with the last
1293        // point overlapping the first point; the last point gets duplicated (and not fused).
1294        // The special treatment for the last point is only applied to Z-ending contours,
1295        // not to open contours.
1296        let paths = make_interpolatable_paths(2, "MLLL", true);
1297        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1298
1299        assert_contour_points(
1300            &glyphs[0],
1301            vec![vec![
1302                CurvePoint::on_curve(0, 0),
1303                CurvePoint::on_curve(1, 1),
1304                CurvePoint::on_curve(2, 2),
1305                CurvePoint::on_curve(0, 0),
1306            ]],
1307        );
1308        assert_contour_points(
1309            &glyphs[1],
1310            vec![vec![
1311                CurvePoint::on_curve(3, 3),
1312                CurvePoint::on_curve(4, 4),
1313                CurvePoint::on_curve(5, 5),
1314                CurvePoint::on_curve(3, 3),
1315            ]],
1316        );
1317    }
1318
1319    #[test]
1320    fn simple_glyphs_from_kurbo_4_lines_closed_duplicate_last_pt() {
1321        for implicit_closing_line in &[true, false] {
1322            // both (closed) paths contain 4 line segments each, but the first path
1323            // looks like a triangle because the last segment has zero length (i.e.
1324            // last and first points are duplicates).
1325            let mut path1 = BezPath::new();
1326            path1.move_to((0.0, 0.0));
1327            path1.line_to((0.0, 1.0));
1328            path1.line_to((1.0, 1.0));
1329            path1.line_to((0.0, 0.0));
1330            if !*implicit_closing_line {
1331                path1.line_to((0.0, 0.0));
1332            }
1333            path1.close_path();
1334
1335            let mut path2 = BezPath::new();
1336            path2.move_to((0.0, 0.0));
1337            path2.line_to((0.0, 2.0));
1338            path2.line_to((2.0, 2.0));
1339            path2.line_to((2.0, 0.0));
1340            if !*implicit_closing_line {
1341                path2.line_to((0.0, 0.0));
1342            }
1343            path2.close_path();
1344
1345            let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
1346
1347            assert_contour_points(
1348                &glyphs[0],
1349                vec![vec![
1350                    CurvePoint::on_curve(0, 0),
1351                    CurvePoint::on_curve(0, 1),
1352                    CurvePoint::on_curve(1, 1),
1353                    CurvePoint::on_curve(0, 0), // duplicate last point retained
1354                ]],
1355            );
1356            assert_contour_points(
1357                &glyphs[1],
1358                vec![vec![
1359                    CurvePoint::on_curve(0, 0),
1360                    CurvePoint::on_curve(0, 2),
1361                    CurvePoint::on_curve(2, 2),
1362                    CurvePoint::on_curve(2, 0),
1363                ]],
1364            );
1365        }
1366    }
1367
1368    #[test]
1369    fn simple_glyphs_from_kurbo_2_quads_1_line_closed_duplicate_last_pt() {
1370        for implicit_closing_line in &[true, false] {
1371            // the closed paths contain 2 quads and 1 line segments, but in the first path
1372            // the last segment has zero length (i.e. last and first points are duplicates).
1373            let mut path1 = BezPath::new();
1374            path1.move_to((0.0, 0.0));
1375            path1.quad_to((0.0, 1.0), (1.0, 1.0));
1376            path1.quad_to((1.0, 0.0), (0.0, 0.0));
1377            if !*implicit_closing_line {
1378                path1.line_to((0.0, 0.0));
1379            }
1380            path1.close_path();
1381
1382            let mut path2 = BezPath::new();
1383            path2.move_to((0.0, 0.0));
1384            path2.quad_to((0.0, 2.0), (2.0, 2.0));
1385            path2.quad_to((2.0, 1.0), (1.0, 0.0));
1386            if !*implicit_closing_line {
1387                path2.line_to((0.0, 0.0));
1388            }
1389            path2.close_path();
1390
1391            let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
1392
1393            assert_contour_points(
1394                &glyphs[0],
1395                vec![vec![
1396                    CurvePoint::on_curve(0, 0),
1397                    CurvePoint::off_curve(0, 1),
1398                    CurvePoint::on_curve(1, 1),
1399                    CurvePoint::off_curve(1, 0),
1400                    CurvePoint::on_curve(0, 0), // duplicate last point retained
1401                ]],
1402            );
1403            assert_contour_points(
1404                &glyphs[1],
1405                vec![vec![
1406                    CurvePoint::on_curve(0, 0),
1407                    CurvePoint::off_curve(0, 2),
1408                    CurvePoint::on_curve(2, 2),
1409                    CurvePoint::off_curve(2, 1),
1410                    CurvePoint::on_curve(1, 0),
1411                ]],
1412            );
1413        }
1414    }
1415
1416    #[test]
1417    fn simple_glyph_from_kurbo_equidistant_but_not_collinear_points() {
1418        let mut path = BezPath::new();
1419        path.move_to((0.0, 0.0));
1420        path.quad_to((2.0, 2.0), (4.0, 3.0));
1421        path.quad_to((6.0, 2.0), (8.0, 0.0));
1422        path.close_path();
1423
1424        let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
1425
1426        assert_contour_points(
1427            &glyph,
1428            vec![vec![
1429                CurvePoint::on_curve(0, 0),
1430                CurvePoint::off_curve(2, 2),
1431                // the following on-curve point is equidistant from the previous/next
1432                // off-curve points but it is not on the same line hence it must NOT
1433                // be dropped
1434                CurvePoint::on_curve(4, 3),
1435                CurvePoint::off_curve(6, 2),
1436                CurvePoint::on_curve(8, 0),
1437            ]],
1438        );
1439    }
1440
1441    #[test]
1442    fn repeatable_flags_basic() {
1443        let flags = [
1444            SimpleGlyphFlags::ON_CURVE_POINT,
1445            SimpleGlyphFlags::X_SHORT_VECTOR,
1446            SimpleGlyphFlags::X_SHORT_VECTOR,
1447        ];
1448        let repeatable = RepeatableFlag::iter_from_flags(flags).collect::<Vec<_>>();
1449        let expected = flags
1450            .into_iter()
1451            .map(|flag| RepeatableFlag { flag, repeat: 0 })
1452            .collect::<Vec<_>>();
1453
1454        // even though we have a repeating flag at the end, we should still produce
1455        // three flags, since we don't bother with repeat counts < 2.
1456        assert_eq!(repeatable, expected);
1457    }
1458
1459    #[test]
1460    fn repeatable_flags_repeats() {
1461        let some_dupes = std::iter::repeat_n(SimpleGlyphFlags::ON_CURVE_POINT, 4);
1462        let many_dupes = std::iter::repeat_n(SimpleGlyphFlags::Y_SHORT_VECTOR, 257);
1463        let repeatable =
1464            RepeatableFlag::iter_from_flags(some_dupes.chain(many_dupes)).collect::<Vec<_>>();
1465        assert_eq!(repeatable.len(), 3);
1466        assert_eq!(
1467            repeatable[0],
1468            RepeatableFlag {
1469                flag: SimpleGlyphFlags::ON_CURVE_POINT | SimpleGlyphFlags::REPEAT_FLAG,
1470                repeat: 3
1471            }
1472        );
1473        assert_eq!(
1474            repeatable[1],
1475            RepeatableFlag {
1476                flag: SimpleGlyphFlags::Y_SHORT_VECTOR | SimpleGlyphFlags::REPEAT_FLAG,
1477                repeat: u8::MAX,
1478            }
1479        );
1480
1481        assert_eq!(
1482            repeatable[2],
1483            RepeatableFlag {
1484                flag: SimpleGlyphFlags::Y_SHORT_VECTOR,
1485                repeat: 0,
1486            }
1487        )
1488    }
1489
1490    #[test]
1491    fn mid_points() {
1492        // exactly in the middle
1493        assert!(is_mid_point(
1494            kurbo::Point::new(0.0, 0.0),
1495            kurbo::Point::new(1.0, 1.0),
1496            kurbo::Point::new(2.0, 2.0)
1497        ));
1498        // in the middle but rounding would make it not; take it anyway
1499        assert!(is_mid_point(
1500            kurbo::Point::new(0.5, 0.5),
1501            kurbo::Point::new(3.0, 3.0),
1502            kurbo::Point::new(5.5, 5.5)
1503        ));
1504        // very close to the middle
1505        assert!(is_mid_point(
1506            kurbo::Point::new(0.0, 0.0),
1507            kurbo::Point::new(1.00001, 0.99999),
1508            kurbo::Point::new(2.0, 2.0)
1509        ));
1510        // not quite in the middle but rounding would make it so; why throw it away?
1511        assert!(is_mid_point(
1512            kurbo::Point::new(0.0, 0.0),
1513            kurbo::Point::new(-1.499999, 0.500001),
1514            kurbo::Point::new(-2.0, 2.0)
1515        ));
1516        // not in the middle, neither before nor after rounding
1517        assert!(!is_mid_point(
1518            kurbo::Point::new(0.0, 0.0),
1519            kurbo::Point::new(1.0, 1.5),
1520            kurbo::Point::new(2.0, 2.0)
1521        ));
1522    }
1523}