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