makepad_font/
outline.rs

1use crate::OutlinePoint;
2use makepad_geometry::{Point, Transform, Transformation};
3use makepad_internal_iter::{
4    ExtendFromInternalIterator, InternalIterator, IntoInternalIterator,
5};
6use makepad_path::PathCommand;
7use std::iter::Cloned;
8use std::slice::Iter;
9
10/// The outline for a glyph.
11///
12/// An outline consists of one or more closed contours, each of which consists of one or more
13/// quadratic b-spline curve segments, which are described by a sequence of outline points.
14#[derive(Clone, Debug, Default, PartialEq)]
15pub struct Outline {
16    contour_ends: Vec<usize>,
17    points: Vec<OutlinePoint>,
18}
19
20impl Outline {
21    /// Creates a new empty outline.
22    pub fn new() -> Outline {
23        Outline::default()
24    }
25
26    /// Returns an iterator over the contours of `self`.
27    pub fn contours(&self) -> Contours {
28        Contours {
29            contour_start: 0,
30            contour_ends: self.contour_ends.iter().cloned(),
31            points: &self.points,
32        }
33    }
34
35    /// Returns an slice of the points of `self`.
36    pub fn points(&self) -> &[OutlinePoint] {
37        &self.points
38    }
39
40    /// Returns an iterator over the path commands that correspond to `self`.
41    pub fn commands(&self) -> Commands {
42        Commands {
43            contours: self.contours(),
44        }
45    }
46
47    /// Returns a mutable slice of the points of `self`.
48    pub fn points_mut(&mut self) -> &mut [OutlinePoint] {
49        &mut self.points
50    }
51
52    /// Returns a builder for a contour.
53    pub fn begin_contour(&mut self) -> ContourBuilder {
54        ContourBuilder {
55            contour_ends: &mut self.contour_ends,
56            points: &mut self.points,
57        }
58    }
59}
60
61impl<'a> ExtendFromInternalIterator<Contour<'a>> for Outline {
62    fn extend_from_internal_iter<I>(&mut self, internal_iter: I)
63    where
64        I: IntoInternalIterator<Item = Contour<'a>>,
65    {
66        internal_iter
67            .into_internal_iter()
68            .for_each(&mut |other_contour| {
69                let mut contour = self.begin_contour();
70                contour.extend_from_internal_iter(other_contour.points().iter().cloned());
71                contour.end();
72                true
73            });
74    }
75}
76
77impl Transform for Outline {
78    fn transform<T>(mut self, t: &T) -> Outline
79    where
80        T: Transformation,
81    {
82        self.transform_mut(t);
83        self
84    }
85
86    fn transform_mut<T>(&mut self, t: &T)
87    where
88        T: Transformation,
89    {
90        for point in self.points_mut() {
91            point.transform_mut(t);
92        }
93    }
94}
95
96/// An iterator over the path commands that correspond to an outline.
97#[derive(Clone, Debug)]
98pub struct Contours<'a> {
99    contour_start: usize,
100    contour_ends: Cloned<Iter<'a, usize>>,
101    points: &'a [OutlinePoint],
102}
103
104impl<'a> Iterator for Contours<'a> {
105    type Item = Contour<'a>;
106
107    fn next(&mut self) -> Option<Contour<'a>> {
108        self.contour_ends.next().map(|contour_end| {
109            let contour_start = self.contour_start;
110            self.contour_start = contour_end;
111            Contour {
112                points: &self.points[contour_start..contour_end],
113            }
114        })
115    }
116}
117
118/// A contour in an outline.
119#[derive(Clone, Copy, Debug)]
120pub struct Contour<'a> {
121    points: &'a [OutlinePoint],
122}
123
124impl<'a> Contour<'a> {
125    pub fn points(&self) -> &'a [OutlinePoint] {
126        &self.points
127    }
128}
129
130/// Returns an iterator over the path commands that correspond to an outline.
131#[derive(Clone, Debug)]
132pub struct Commands<'a> {
133    contours: Contours<'a>,
134}
135
136impl<'a> InternalIterator for Commands<'a> {
137    type Item = PathCommand;
138
139    fn for_each<F>(self, f: &mut F) -> bool
140    where
141        F: FnMut(PathCommand) -> bool,
142    {
143        // To convert a sequence of quadratic b-spline curve segments to a sequence of quadratic
144        // Bezier curve segments, we need to insert a new endpoint at the midpoint of each pair
145        // of adjacent off curve points.
146        for contour in self.contours {
147            // The off curve point we encountered before the first on curve point, if it exists.
148            let mut first_off_curve_point: Option<Point> = None;
149            // The first on curve point we encountered.
150            let mut first_on_curve_point: Option<Point> = None;
151            // The last off curve point we encountered.
152            let mut last_off_curve_point: Option<Point> = None;
153            for point in contour.points() {
154                if first_on_curve_point.is_none() {
155                    if point.is_on_curve {
156                        if !f(PathCommand::MoveTo(point.point)) {
157                            return false;
158                        }
159                        first_on_curve_point = Some(point.point);
160                    } else {
161                        if let Some(first_off_curve_point) = first_off_curve_point {
162                            let midpoint = first_off_curve_point.lerp(point.point, 0.5);
163                            if !f(PathCommand::MoveTo(midpoint)) {
164                                return false;
165                            }
166                            first_on_curve_point = Some(midpoint);
167                            last_off_curve_point = Some(point.point);
168                        } else {
169                            first_off_curve_point = Some(point.point);
170                        }
171                    }
172                } else {
173                    match (last_off_curve_point, point.is_on_curve) {
174                        (None, false) => {
175                            last_off_curve_point = Some(point.point);
176                        }
177                        (None, true) => {
178                            if !f(PathCommand::LineTo(point.point)) {
179                                return false;
180                            }
181                        }
182                        (Some(last_point), false) => {
183                            if !f(PathCommand::QuadraticTo(
184                                last_point,
185                                last_point.lerp(point.point, 0.5),
186                            )) {
187                                return false;
188                            }
189                            last_off_curve_point = Some(point.point);
190                        }
191                        (Some(last_point), true) => {
192                            if !f(PathCommand::QuadraticTo(last_point, point.point)) {
193                                return false;
194                            }
195                            last_off_curve_point = None;
196                        }
197                    }
198                }
199            }
200            if let Some(first_on_curve_point) = first_on_curve_point {
201                match (last_off_curve_point, first_off_curve_point) {
202                    (None, None) => {
203                        if !f(PathCommand::LineTo(first_on_curve_point)) {
204                            return false;
205                        }
206                    }
207                    (None, Some(first_off_curve_point)) => {
208                        if !f(PathCommand::QuadraticTo(
209                            first_off_curve_point,
210                            first_on_curve_point,
211                        )) {
212                            return false;
213                        }
214                    }
215                    (Some(last_point), None) => {
216                        if !f(PathCommand::QuadraticTo(last_point, first_on_curve_point)) {
217                            return false;
218                        }
219                    }
220                    (Some(last_point), Some(first_off_curve_point)) => {
221                        let midpoint = last_point.lerp(first_off_curve_point, 0.5);
222                        if !f(PathCommand::QuadraticTo(last_point, midpoint)) {
223                            return false;
224                        }
225                        if !f(PathCommand::QuadraticTo(
226                            first_off_curve_point,
227                            first_on_curve_point,
228                        )) {
229                            return false;
230                        }
231                    }
232                }
233                if !f(PathCommand::Close) {
234                    return false;
235                }
236            }
237        }
238        true
239    }
240}
241
242#[derive(Debug)]
243pub struct ContourBuilder<'a> {
244    contour_ends: &'a mut Vec<usize>,
245    points: &'a mut Vec<OutlinePoint>,
246}
247
248impl<'a> ContourBuilder<'a> {
249    pub fn end(self) {}
250
251    pub fn push(&mut self, point: OutlinePoint) {
252        self.points.push(point);
253    }
254}
255
256impl<'a> Drop for ContourBuilder<'a> {
257    fn drop(&mut self) {
258        if self.points.len() != self.contour_ends.last().cloned().unwrap_or(0) {
259            self.contour_ends.push(self.points.len());
260        }
261    }
262}
263
264impl<'a> ExtendFromInternalIterator<OutlinePoint> for ContourBuilder<'a> {
265    fn extend_from_internal_iter<I>(&mut self, internal_iter: I)
266    where
267        I: IntoInternalIterator<Item = OutlinePoint>,
268    {
269        internal_iter.into_internal_iter().for_each(&mut |point| {
270            self.push(point);
271            true
272        });
273    }
274}