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