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) =
673            loca.get_glyf(GlyphId::new(0), &glyf).unwrap().unwrap()
674        else {
675            panic!("not a simple glyph")
676        };
677        let orig_bytes = orig.offset_data();
678
679        let ours = SimpleGlyph::from_table_ref(&orig);
680        let bytes = pad_for_loca_format(&loca, crate::dump_table(&ours).unwrap());
681        let ours = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
682
683        let our_points = ours.points().collect::<Vec<_>>();
684        let their_points = orig.points().collect::<Vec<_>>();
685        assert_eq!(our_points, their_points);
686        assert_eq!(orig_bytes.as_ref(), bytes);
687        assert_eq!(orig.glyph_data(), ours.glyph_data());
688        assert_eq!(orig_bytes.len(), bytes.len());
689    }
690
691    #[test]
692    fn round_trip_simple() {
693        let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
694        let loca = font.loca(None).unwrap();
695        let glyf = font.glyf().unwrap();
696        let read_glyf::Glyph::Simple(orig) =
697            loca.get_glyf(GlyphId::new(2), &glyf).unwrap().unwrap()
698        else {
699            panic!("not a simple glyph")
700        };
701        let orig_bytes = orig.offset_data();
702
703        let bezpath = BezPath::from_svg("M278,710 L278,470 L998,470 L998,710 Z").unwrap();
704
705        let ours = SimpleGlyph::from_bezpath(&bezpath).unwrap();
706        let bytes = pad_for_loca_format(&loca, crate::dump_table(&ours).unwrap());
707        let ours = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
708
709        let our_points = ours.points().collect::<Vec<_>>();
710        let their_points = orig.points().collect::<Vec<_>>();
711        assert_eq!(our_points, their_points);
712        assert_eq!(orig_bytes.as_ref(), bytes);
713        assert_eq!(orig.glyph_data(), ours.glyph_data());
714        assert_eq!(orig_bytes.len(), bytes.len());
715    }
716
717    #[test]
718    fn round_trip_multi_contour() {
719        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
720        let loca = font.loca(None).unwrap();
721        let glyf = font.glyf().unwrap();
722        let read_glyf::Glyph::Simple(orig) =
723            loca.get_glyf(GlyphId::new(1), &glyf).unwrap().unwrap()
724        else {
725            panic!("not a simple glyph")
726        };
727        let orig_bytes = orig.offset_data();
728
729        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();
730
731        let ours = SimpleGlyph::from_bezpath(&bezpath).unwrap();
732        let bytes = pad_for_loca_format(&loca, crate::dump_table(&ours).unwrap());
733        let ours = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
734
735        let our_points = ours.points().collect::<Vec<_>>();
736        let their_points = orig.points().collect::<Vec<_>>();
737        dbg!(
738            SimpleGlyphFlags::from_bits(1),
739            SimpleGlyphFlags::from_bits(9)
740        );
741        assert_eq!(our_points, their_points);
742        assert_eq!(orig.glyph_data(), ours.glyph_data());
743        assert_eq!(orig_bytes.len(), bytes.len());
744        assert_eq!(orig_bytes.as_ref(), bytes);
745    }
746
747    #[test]
748    fn simple_glyph_open_path() {
749        let mut path = BezPath::new();
750        path.move_to((20., -100.));
751        path.quad_to((1337., 1338.), (-50., -69.0));
752        path.quad_to((13., 255.), (-255., 256.));
753        // even if the last point is on top of the first, the path was not deliberately closed
754        // hence there is going to be an extra point (6, not 5 in total)
755        path.line_to((20., -100.));
756
757        let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
758        let bytes = crate::dump_table(&glyph).unwrap();
759        let read = read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
760        assert_eq!(read.number_of_contours(), 1);
761        assert_eq!(read.num_points(), 6);
762        assert_eq!(read.end_pts_of_contours(), &[5]);
763        let points = read.points().collect::<Vec<_>>();
764        assert_eq!(points[0].x, 20);
765        assert_eq!(points[0].y, -100);
766        assert!(points[0].on_curve);
767        assert_eq!(points[1].x, 1337);
768        assert_eq!(points[1].y, 1338);
769        assert!(!points[1].on_curve);
770        assert_eq!(points[4].x, -255);
771        assert_eq!(points[4].y, 256);
772        assert!(points[4].on_curve);
773        assert_eq!(points[5].x, 20);
774        assert_eq!(points[5].y, -100);
775        assert!(points[5].on_curve);
776    }
777
778    #[test]
779    fn simple_glyph_closed_path_implicit_vs_explicit_closing_line() {
780        let mut path1 = BezPath::new();
781        path1.move_to((20., -100.));
782        path1.quad_to((1337., 1338.), (-50., -69.0));
783        path1.quad_to((13., 255.), (-255., 256.));
784        path1.close_path();
785
786        let mut path2 = BezPath::new();
787        path2.move_to((20., -100.));
788        path2.quad_to((1337., 1338.), (-50., -69.0));
789        path2.quad_to((13., 255.), (-255., 256.));
790        // this line_to (absent from path1) makes no difference since in both cases the
791        // path is closed with a close_path (5 points in total, not 6)
792        path2.line_to((20., -100.));
793        path2.close_path();
794
795        for path in &[path1, path2] {
796            let glyph = SimpleGlyph::from_bezpath(path).unwrap();
797            let bytes = crate::dump_table(&glyph).unwrap();
798            let read =
799                read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
800            assert_eq!(read.number_of_contours(), 1);
801            assert_eq!(read.num_points(), 5);
802            assert_eq!(read.end_pts_of_contours(), &[4]);
803            let points = read.points().collect::<Vec<_>>();
804            assert_eq!(points[0].x, 20);
805            assert_eq!(points[0].y, -100);
806            assert!(points[0].on_curve);
807            assert_eq!(points[1].x, 1337);
808            assert_eq!(points[1].y, 1338);
809            assert!(!points[1].on_curve);
810            assert_eq!(points[4].x, -255);
811            assert_eq!(points[4].y, 256);
812            assert!(points[4].on_curve);
813        }
814    }
815
816    #[test]
817    fn keep_single_point_contours() {
818        // single points may be meaningless, but are also harmless
819        let mut path = BezPath::new();
820        path.move_to((0.0, 0.0));
821        // path.close_path();  // doesn't really matter if this is closed
822        path.move_to((1.0, 2.0));
823        path.close_path();
824
825        let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
826        let bytes = crate::dump_table(&glyph).unwrap();
827        let read = read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
828        assert_eq!(read.number_of_contours(), 2);
829        assert_eq!(read.num_points(), 2);
830        assert_eq!(read.end_pts_of_contours(), &[0, 1]);
831        let points = read.points().collect::<Vec<_>>();
832        assert_eq!(points[0].x, 0);
833        assert_eq!(points[0].y, 0);
834        assert!(points[0].on_curve);
835        assert_eq!(points[1].x, 1);
836        assert_eq!(points[1].y, 2);
837        assert!(points[0].on_curve);
838    }
839
840    #[test]
841    fn compile_repeatable_flags() {
842        let mut path = BezPath::new();
843        path.move_to((20., -100.));
844        path.line_to((25., -90.));
845        path.line_to((50., -69.));
846        path.line_to((80., -20.));
847
848        let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
849        let flags = glyph
850            .compute_point_deltas()
851            .map(|x| x.0)
852            .collect::<Vec<_>>();
853        let r_flags = RepeatableFlag::iter_from_flags(flags.iter().copied()).collect::<Vec<_>>();
854
855        assert_eq!(r_flags.len(), 2, "{r_flags:?}");
856        let bytes = crate::dump_table(&glyph).unwrap();
857        let read = read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
858        assert_eq!(read.number_of_contours(), 1);
859        assert_eq!(read.num_points(), 4);
860        assert_eq!(read.end_pts_of_contours(), &[3]);
861        let points = read.points().collect::<Vec<_>>();
862        assert_eq!(points[0].x, 20);
863        assert_eq!(points[0].y, -100);
864        assert_eq!(points[1].x, 25);
865        assert_eq!(points[1].y, -90);
866        assert_eq!(points[2].x, 50);
867        assert_eq!(points[2].y, -69);
868        assert_eq!(points[3].x, 80);
869        assert_eq!(points[3].y, -20);
870    }
871
872    #[test]
873    fn simple_glyphs_from_kurbo_unequal_number_of_elements() {
874        let mut path1 = BezPath::new();
875        path1.move_to((0., 0.));
876        path1.line_to((1., 1.));
877        path1.line_to((2., 2.));
878        path1.line_to((0., 0.));
879        path1.close_path();
880        assert_eq!(path1.elements().len(), 5);
881
882        let mut path2 = BezPath::new();
883        path2.move_to((3., 3.));
884        path2.line_to((4., 4.));
885        path2.line_to((5., 5.));
886        path2.line_to((6., 6.));
887        path2.line_to((3., 3.));
888        path2.close_path();
889        assert_eq!(path2.elements().len(), 6);
890
891        let err = simple_glyphs_from_kurbo(&[path1, path2]).unwrap_err();
892        assert!(matches!(err, MalformedPath::UnequalNumberOfElements(_)));
893        assert_eq!(format!("{:?}", err), "UnequalNumberOfElements([5, 6])");
894    }
895
896    #[test]
897    fn simple_glyphs_from_kurbo_inconsistent_path_elements() {
898        let mut path1 = BezPath::new();
899        path1.move_to((0., 0.));
900        path1.line_to((1., 1.));
901        path1.quad_to((2., 2.), (0., 0.));
902        path1.close_path();
903        let mut path2 = BezPath::new();
904        path2.move_to((3., 3.));
905        path2.quad_to((4., 4.), (5., 5.)); // elements at index 1 are inconsistent
906        path2.line_to((3., 3.));
907        path2.close_path();
908
909        let err = simple_glyphs_from_kurbo(&[path1, path2]).unwrap_err();
910        assert!(matches!(err, MalformedPath::InconsistentPathElements(1, _)));
911        assert_eq!(
912            format!("{:?}", err),
913            "InconsistentPathElements(1, [\"L\", \"Q\"])"
914        );
915    }
916
917    /// Create a number of interpolatable BezPaths with the given element types.
918    /// The paths will be identical except for the point coordinates of the elements,
919    /// which will be (0.0, 0.0), (1.0, 1.0), (2.0, 2.0), etc. for each subsequent
920    /// element. If `last_pt_equal_move` is true, the last point of each sub-path
921    /// will be equal to the first (M) point of that sub-path.
922    /// E.g.:
923    /// ```
924    /// let paths = make_interpolatable_paths(2, "MLLZ", false);
925    /// println!("{:?}", paths[0].to_svg());
926    /// // "M0,0 L1,1 L2,2 Z"
927    /// println!("{:?}", paths[1].to_svg());
928    /// // "M3,3 L4,4 L5,5 Z"
929    /// let paths = make_interpolatable_paths(3, "MLLLZMQQZ", true);
930    /// println!("{:?}", paths[0].to_svg());
931    /// // "M0,0 L1,1 L2,2 L0,0 Z M3,3 Q4,4 5,5 Q6,6 3,3 Z"
932    /// println!("{:?}", paths[1].to_svg());
933    /// // "M7,7 L8,8 L9,9 L7,7 Z M10,10 Q11,11 12,12 Q13,13 10,10 Z"
934    /// println!("{:?}", paths[2].to_svg());
935    /// // "M14,14 L15,15 L16,16 L14,14 Z M17,17 Q18,18 19,19 Q20,20 17,17 Z"
936    /// ```
937    fn make_interpolatable_paths(
938        num_paths: usize,
939        el_types: &str,
940        last_pt_equal_move: bool,
941    ) -> Vec<BezPath> {
942        let mut paths = Vec::new();
943        // we don't care about the actual coordinate values, just use a counter
944        // that yields 0.0, 1.0, 2.0, 3.0, etc.
945        let mut start = 0.0;
946        let mut points = std::iter::from_fn(move || {
947            let value = start;
948            start += 1.0;
949            Some((value, value))
950        });
951        let el_types = el_types.chars().collect::<Vec<_>>();
952        assert!(!el_types.is_empty());
953        for _ in 0..num_paths {
954            let mut path = BezPath::new();
955            let mut start_pt = None;
956            // use peekable iterator so we can look ahead to next el_type
957            let mut el_types_iter = el_types.iter().peekable();
958            while let Some(&el_type) = el_types_iter.next() {
959                let next_el_type = el_types_iter.peek().map(|x| **x).unwrap_or('M');
960                match el_type {
961                    'M' => {
962                        start_pt = points.next();
963                        path.move_to(start_pt.unwrap());
964                    }
965                    'L' => {
966                        if matches!(next_el_type, 'Z' | 'M') && last_pt_equal_move {
967                            path.line_to(start_pt.unwrap());
968                        } else {
969                            path.line_to(points.next().unwrap());
970                        }
971                    }
972                    'Q' => {
973                        let p1 = points.next().unwrap();
974                        let p2 = if matches!(next_el_type, 'Z' | 'M') && last_pt_equal_move {
975                            start_pt.unwrap()
976                        } else {
977                            points.next().unwrap()
978                        };
979                        path.quad_to(p1, p2);
980                    }
981                    'Z' => {
982                        path.close_path();
983                        start_pt = None;
984                    }
985                    _ => panic!("Unsupported element type {:?}", el_type),
986                }
987            }
988            paths.push(path);
989        }
990        assert_eq!(paths.len(), num_paths);
991        paths
992    }
993
994    fn assert_contour_points(glyph: &SimpleGlyph, all_points: Vec<Vec<CurvePoint>>) {
995        let expected_num_contours = all_points.len();
996        assert_eq!(glyph.contours.len(), expected_num_contours);
997        for (contour, expected_points) in glyph.contours.iter().zip(all_points.iter()) {
998            let points = contour.iter().copied().collect::<Vec<_>>();
999            assert_eq!(points, *expected_points);
1000        }
1001    }
1002
1003    #[test]
1004    fn simple_glyphs_from_kurbo_3_lines_closed() {
1005        // two triangles, each with 3 lines, explicitly closed
1006        let paths = make_interpolatable_paths(2, "MLLLZ", true);
1007        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1008
1009        assert_contour_points(
1010            &glyphs[0],
1011            vec![vec![
1012                CurvePoint::on_curve(0, 0),
1013                CurvePoint::on_curve(1, 1),
1014                CurvePoint::on_curve(2, 2),
1015            ]],
1016        );
1017        assert_contour_points(
1018            &glyphs[1],
1019            vec![vec![
1020                CurvePoint::on_curve(3, 3),
1021                CurvePoint::on_curve(4, 4),
1022                CurvePoint::on_curve(5, 5),
1023            ]],
1024        );
1025    }
1026
1027    #[test]
1028    fn simple_glyphs_from_kurbo_3_lines_implicitly_closed() {
1029        // two triangles, each with 2 lines plus the last implicit closing line
1030        let paths = make_interpolatable_paths(2, "MLLZ", false);
1031        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1032
1033        assert_contour_points(
1034            &glyphs[0],
1035            vec![vec![
1036                CurvePoint::on_curve(0, 0),
1037                CurvePoint::on_curve(1, 1),
1038                CurvePoint::on_curve(2, 2),
1039            ]],
1040        );
1041        assert_contour_points(
1042            &glyphs[1],
1043            vec![vec![
1044                CurvePoint::on_curve(3, 3),
1045                CurvePoint::on_curve(4, 4),
1046                CurvePoint::on_curve(5, 5),
1047            ]],
1048        );
1049    }
1050
1051    #[test]
1052    fn simple_glyphs_from_kurbo_2_quads_closed() {
1053        // two compatible paths each containing 2 consecutive quadratic bezier curves,
1054        // where the respective off-curves are placed at equal distance from the on-curve
1055        // point joining them; the paths are closed and the last quad point is the same
1056        // as the move point.
1057        let paths = make_interpolatable_paths(2, "MQQZ", true);
1058        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1059
1060        assert_contour_points(
1061            &glyphs[0],
1062            vec![vec![
1063                CurvePoint::on_curve(0, 0),
1064                CurvePoint::off_curve(1, 1),
1065                // CurvePoint::on_curve(2, 2),  // implied oncurve point dropped
1066                CurvePoint::off_curve(3, 3),
1067            ]],
1068        );
1069        assert_contour_points(
1070            &glyphs[1],
1071            vec![vec![
1072                CurvePoint::on_curve(4, 4),
1073                CurvePoint::off_curve(5, 5),
1074                // CurvePoint::on_curve(6, 6),  // implied
1075                CurvePoint::off_curve(7, 7),
1076            ]],
1077        );
1078    }
1079
1080    #[test]
1081    fn simple_glyphs_from_kurbo_2_quads_1_line_implicitly_closed() {
1082        // same path elements as above 'MQQZ' but with the last_pt_equal_move=false
1083        // thus this actually contains three segments: 2 quads plus the last implied
1084        // closing line. There is an additional on-curve point at the end of the path.
1085        let paths = make_interpolatable_paths(2, "MQQZ", false);
1086        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1087
1088        assert_contour_points(
1089            &glyphs[0],
1090            vec![vec![
1091                CurvePoint::on_curve(0, 0),
1092                CurvePoint::off_curve(1, 1),
1093                // CurvePoint::on_curve(2, 2),
1094                CurvePoint::off_curve(3, 3),
1095                CurvePoint::on_curve(4, 4),
1096            ]],
1097        );
1098        assert_contour_points(
1099            &glyphs[1],
1100            vec![vec![
1101                CurvePoint::on_curve(5, 5),
1102                CurvePoint::off_curve(6, 6),
1103                // CurvePoint::on_curve(7, 7),
1104                CurvePoint::off_curve(8, 8),
1105                CurvePoint::on_curve(9, 9),
1106            ]],
1107        );
1108    }
1109
1110    #[test]
1111    fn simple_glyphs_from_kurbo_multiple_contours_mixed_segments() {
1112        // four paths, each containing two sub-paths, with a mix of line and quad segments
1113        let paths = make_interpolatable_paths(4, "MLQQZMQLQLZ", true);
1114        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1115
1116        assert_contour_points(
1117            &glyphs[0],
1118            vec![
1119                vec![
1120                    CurvePoint::on_curve(0, 0),
1121                    CurvePoint::on_curve(1, 1),
1122                    CurvePoint::off_curve(2, 2),
1123                    // CurvePoint::on_curve(3, 3),
1124                    CurvePoint::off_curve(4, 4),
1125                ],
1126                vec![
1127                    CurvePoint::on_curve(5, 5),
1128                    CurvePoint::off_curve(6, 6),
1129                    CurvePoint::on_curve(7, 7),
1130                    CurvePoint::on_curve(8, 8),
1131                    CurvePoint::off_curve(9, 9),
1132                    CurvePoint::on_curve(10, 10),
1133                ],
1134            ],
1135        );
1136    }
1137
1138    #[test]
1139    fn simple_glyphs_from_kurbo_all_quad_off_curves() {
1140        // the following path contains only quadratic curves and all the on-curve points
1141        // can be implied, thus the resulting glyf contours contain only off-curves.
1142        let mut path1 = BezPath::new();
1143        path1.move_to((0.0, 1.0));
1144        path1.quad_to((1.0, 1.0), (1.0, 0.0));
1145        path1.quad_to((1.0, -1.0), (0.0, -1.0));
1146        path1.quad_to((-1.0, -1.0), (-1.0, 0.0));
1147        path1.quad_to((-1.0, 1.0), (0.0, 1.0));
1148        path1.close_path();
1149
1150        let mut path2 = path1.clone();
1151        path2.apply_affine(Affine::scale(2.0));
1152
1153        let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
1154
1155        assert_contour_points(
1156            &glyphs[0],
1157            vec![vec![
1158                CurvePoint::off_curve(1, 1),
1159                CurvePoint::off_curve(1, -1),
1160                CurvePoint::off_curve(-1, -1),
1161                CurvePoint::off_curve(-1, 1),
1162            ]],
1163        );
1164        assert_contour_points(
1165            &glyphs[1],
1166            vec![vec![
1167                CurvePoint::off_curve(2, 2),
1168                CurvePoint::off_curve(2, -2),
1169                CurvePoint::off_curve(-2, -2),
1170                CurvePoint::off_curve(-2, 2),
1171            ]],
1172        );
1173    }
1174
1175    #[test]
1176    fn simple_glyphs_from_kurbo_keep_on_curve_unless_impliable_for_all() {
1177        let mut path1 = BezPath::new();
1178        path1.move_to((0.0, 0.0));
1179        path1.quad_to((0.0, 1.0), (1.0, 1.0)); // on-curve equidistant from prev/next off-curves
1180        path1.quad_to((2.0, 1.0), (2.0, 0.0));
1181        path1.line_to((0.0, 0.0));
1182        path1.close_path();
1183
1184        // when making a SimpleGlyph from this path alone, the on-curve point at (1, 1)
1185        // can be implied/dropped.
1186        assert_contour_points(
1187            &SimpleGlyph::from_bezpath(&path1).unwrap(),
1188            vec![vec![
1189                CurvePoint::on_curve(0, 0),
1190                CurvePoint::off_curve(0, 1),
1191                // CurvePoint::on_curve(1, 1),  // implied
1192                CurvePoint::off_curve(2, 1),
1193                CurvePoint::on_curve(2, 0),
1194            ]],
1195        );
1196
1197        let mut path2 = BezPath::new();
1198        path2.move_to((0.0, 0.0));
1199        path2.quad_to((0.0, 2.0), (2.0, 2.0)); // on-curve NOT equidistant from prev/next off-curves
1200        path2.quad_to((3.0, 2.0), (3.0, 0.0));
1201        path2.line_to((0.0, 0.0));
1202        path2.close_path();
1203
1204        let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
1205
1206        // However, when making interpolatable SimpleGlyphs from both paths, the on-curve
1207        // can no longer be implied/dropped (for it is not impliable in the second path).
1208        assert_contour_points(
1209            &glyphs[0],
1210            vec![vec![
1211                CurvePoint::on_curve(0, 0),
1212                CurvePoint::off_curve(0, 1),
1213                CurvePoint::on_curve(1, 1), // NOT implied
1214                CurvePoint::off_curve(2, 1),
1215                CurvePoint::on_curve(2, 0),
1216            ]],
1217        );
1218        assert_contour_points(
1219            &glyphs[1],
1220            vec![vec![
1221                CurvePoint::on_curve(0, 0),
1222                CurvePoint::off_curve(0, 2),
1223                CurvePoint::on_curve(2, 2), // NOT implied
1224                CurvePoint::off_curve(3, 2),
1225                CurvePoint::on_curve(3, 0),
1226            ]],
1227        );
1228    }
1229
1230    #[test]
1231    fn simple_glyphs_from_kurbo_2_lines_open() {
1232        // these contours contain two lines each and are not closed (no 'Z'); they still
1233        // produce three points and are treated as closed for the sake of TrueType glyf.
1234        let paths = make_interpolatable_paths(2, "MLL", false);
1235        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1236
1237        assert_contour_points(
1238            &glyphs[0],
1239            vec![vec![
1240                CurvePoint::on_curve(0, 0),
1241                CurvePoint::on_curve(1, 1),
1242                CurvePoint::on_curve(2, 2),
1243            ]],
1244        );
1245        assert_contour_points(
1246            &glyphs[1],
1247            vec![vec![
1248                CurvePoint::on_curve(3, 3),
1249                CurvePoint::on_curve(4, 4),
1250                CurvePoint::on_curve(5, 5),
1251            ]],
1252        );
1253    }
1254
1255    #[test]
1256    fn simple_glyphs_from_kurbo_3_lines_open_duplicate_last_pt() {
1257        // two paths with one open contour, containing three line segments with the last
1258        // point overlapping the first point; the last point gets duplicated (and not fused).
1259        // The special treatment for the last point is only applied to Z-ending contours,
1260        // not to open contours.
1261        let paths = make_interpolatable_paths(2, "MLLL", true);
1262        let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
1263
1264        assert_contour_points(
1265            &glyphs[0],
1266            vec![vec![
1267                CurvePoint::on_curve(0, 0),
1268                CurvePoint::on_curve(1, 1),
1269                CurvePoint::on_curve(2, 2),
1270                CurvePoint::on_curve(0, 0),
1271            ]],
1272        );
1273        assert_contour_points(
1274            &glyphs[1],
1275            vec![vec![
1276                CurvePoint::on_curve(3, 3),
1277                CurvePoint::on_curve(4, 4),
1278                CurvePoint::on_curve(5, 5),
1279                CurvePoint::on_curve(3, 3),
1280            ]],
1281        );
1282    }
1283
1284    #[test]
1285    fn simple_glyphs_from_kurbo_4_lines_closed_duplicate_last_pt() {
1286        for implicit_closing_line in &[true, false] {
1287            // both (closed) paths contain 4 line segments each, but the first path
1288            // looks like a triangle because the last segment has zero length (i.e.
1289            // last and first points are duplicates).
1290            let mut path1 = BezPath::new();
1291            path1.move_to((0.0, 0.0));
1292            path1.line_to((0.0, 1.0));
1293            path1.line_to((1.0, 1.0));
1294            path1.line_to((0.0, 0.0));
1295            if !*implicit_closing_line {
1296                path1.line_to((0.0, 0.0));
1297            }
1298            path1.close_path();
1299
1300            let mut path2 = BezPath::new();
1301            path2.move_to((0.0, 0.0));
1302            path2.line_to((0.0, 2.0));
1303            path2.line_to((2.0, 2.0));
1304            path2.line_to((2.0, 0.0));
1305            if !*implicit_closing_line {
1306                path2.line_to((0.0, 0.0));
1307            }
1308            path2.close_path();
1309
1310            let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
1311
1312            assert_contour_points(
1313                &glyphs[0],
1314                vec![vec![
1315                    CurvePoint::on_curve(0, 0),
1316                    CurvePoint::on_curve(0, 1),
1317                    CurvePoint::on_curve(1, 1),
1318                    CurvePoint::on_curve(0, 0), // duplicate last point retained
1319                ]],
1320            );
1321            assert_contour_points(
1322                &glyphs[1],
1323                vec![vec![
1324                    CurvePoint::on_curve(0, 0),
1325                    CurvePoint::on_curve(0, 2),
1326                    CurvePoint::on_curve(2, 2),
1327                    CurvePoint::on_curve(2, 0),
1328                ]],
1329            );
1330        }
1331    }
1332
1333    #[test]
1334    fn simple_glyphs_from_kurbo_2_quads_1_line_closed_duplicate_last_pt() {
1335        for implicit_closing_line in &[true, false] {
1336            // the closed paths contain 2 quads and 1 line segments, but in the first path
1337            // the last segment has zero length (i.e. last and first points are duplicates).
1338            let mut path1 = BezPath::new();
1339            path1.move_to((0.0, 0.0));
1340            path1.quad_to((0.0, 1.0), (1.0, 1.0));
1341            path1.quad_to((1.0, 0.0), (0.0, 0.0));
1342            if !*implicit_closing_line {
1343                path1.line_to((0.0, 0.0));
1344            }
1345            path1.close_path();
1346
1347            let mut path2 = BezPath::new();
1348            path2.move_to((0.0, 0.0));
1349            path2.quad_to((0.0, 2.0), (2.0, 2.0));
1350            path2.quad_to((2.0, 1.0), (1.0, 0.0));
1351            if !*implicit_closing_line {
1352                path2.line_to((0.0, 0.0));
1353            }
1354            path2.close_path();
1355
1356            let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
1357
1358            assert_contour_points(
1359                &glyphs[0],
1360                vec![vec![
1361                    CurvePoint::on_curve(0, 0),
1362                    CurvePoint::off_curve(0, 1),
1363                    CurvePoint::on_curve(1, 1),
1364                    CurvePoint::off_curve(1, 0),
1365                    CurvePoint::on_curve(0, 0), // duplicate last point retained
1366                ]],
1367            );
1368            assert_contour_points(
1369                &glyphs[1],
1370                vec![vec![
1371                    CurvePoint::on_curve(0, 0),
1372                    CurvePoint::off_curve(0, 2),
1373                    CurvePoint::on_curve(2, 2),
1374                    CurvePoint::off_curve(2, 1),
1375                    CurvePoint::on_curve(1, 0),
1376                ]],
1377            );
1378        }
1379    }
1380
1381    #[test]
1382    fn simple_glyph_from_kurbo_equidistant_but_not_collinear_points() {
1383        let mut path = BezPath::new();
1384        path.move_to((0.0, 0.0));
1385        path.quad_to((2.0, 2.0), (4.0, 3.0));
1386        path.quad_to((6.0, 2.0), (8.0, 0.0));
1387        path.close_path();
1388
1389        let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
1390
1391        assert_contour_points(
1392            &glyph,
1393            vec![vec![
1394                CurvePoint::on_curve(0, 0),
1395                CurvePoint::off_curve(2, 2),
1396                // the following on-curve point is equidistant from the previous/next
1397                // off-curve points but it is not on the same line hence it must NOT
1398                // be dropped
1399                CurvePoint::on_curve(4, 3),
1400                CurvePoint::off_curve(6, 2),
1401                CurvePoint::on_curve(8, 0),
1402            ]],
1403        );
1404    }
1405
1406    #[test]
1407    fn repeatable_flags_basic() {
1408        let flags = [
1409            SimpleGlyphFlags::ON_CURVE_POINT,
1410            SimpleGlyphFlags::X_SHORT_VECTOR,
1411            SimpleGlyphFlags::X_SHORT_VECTOR,
1412        ];
1413        let repeatable = RepeatableFlag::iter_from_flags(flags).collect::<Vec<_>>();
1414        let expected = flags
1415            .into_iter()
1416            .map(|flag| RepeatableFlag { flag, repeat: 0 })
1417            .collect::<Vec<_>>();
1418
1419        // even though we have a repeating flag at the end, we should still produce
1420        // three flags, since we don't bother with repeat counts < 2.
1421        assert_eq!(repeatable, expected);
1422    }
1423
1424    #[test]
1425    fn repeatable_flags_repeats() {
1426        let some_dupes = std::iter::repeat_n(SimpleGlyphFlags::ON_CURVE_POINT, 4);
1427        let many_dupes = std::iter::repeat_n(SimpleGlyphFlags::Y_SHORT_VECTOR, 257);
1428        let repeatable =
1429            RepeatableFlag::iter_from_flags(some_dupes.chain(many_dupes)).collect::<Vec<_>>();
1430        assert_eq!(repeatable.len(), 3);
1431        assert_eq!(
1432            repeatable[0],
1433            RepeatableFlag {
1434                flag: SimpleGlyphFlags::ON_CURVE_POINT | SimpleGlyphFlags::REPEAT_FLAG,
1435                repeat: 3
1436            }
1437        );
1438        assert_eq!(
1439            repeatable[1],
1440            RepeatableFlag {
1441                flag: SimpleGlyphFlags::Y_SHORT_VECTOR | SimpleGlyphFlags::REPEAT_FLAG,
1442                repeat: u8::MAX,
1443            }
1444        );
1445
1446        assert_eq!(
1447            repeatable[2],
1448            RepeatableFlag {
1449                flag: SimpleGlyphFlags::Y_SHORT_VECTOR,
1450                repeat: 0,
1451            }
1452        )
1453    }
1454
1455    #[test]
1456    fn mid_points() {
1457        // exactly in the middle
1458        assert!(is_mid_point(
1459            kurbo::Point::new(0.0, 0.0),
1460            kurbo::Point::new(1.0, 1.0),
1461            kurbo::Point::new(2.0, 2.0)
1462        ));
1463        // in the middle but rounding would make it not; take it anyway
1464        assert!(is_mid_point(
1465            kurbo::Point::new(0.5, 0.5),
1466            kurbo::Point::new(3.0, 3.0),
1467            kurbo::Point::new(5.5, 5.5)
1468        ));
1469        // very close to the middle
1470        assert!(is_mid_point(
1471            kurbo::Point::new(0.0, 0.0),
1472            kurbo::Point::new(1.00001, 0.99999),
1473            kurbo::Point::new(2.0, 2.0)
1474        ));
1475        // not quite in the middle but rounding would make it so; why throw it away?
1476        assert!(is_mid_point(
1477            kurbo::Point::new(0.0, 0.0),
1478            kurbo::Point::new(-1.499999, 0.500001),
1479            kurbo::Point::new(-2.0, 2.0)
1480        ));
1481        // not in the middle, neither before nor after rounding
1482        assert!(!is_mid_point(
1483            kurbo::Point::new(0.0, 0.0),
1484            kurbo::Point::new(1.0, 1.5),
1485            kurbo::Point::new(2.0, 2.0)
1486        ));
1487    }
1488}