1use super::PathSink;
10use crate::tables::glyf::PointFlags;
11
12pub trait UnscaledOutlineSink {
14 fn start_contour(&mut self);
16
17 fn point(&mut self, x: i32, y: i32, flags: PointFlags);
23
24 fn end_contour(&mut self);
26}
27
28impl<S: UnscaledOutlineSink + ?Sized> UnscaledOutlineSink for &mut S {
29 fn start_contour(&mut self) {
30 (**self).start_contour()
31 }
32
33 fn point(&mut self, x: i32, y: i32, flags: PointFlags) {
34 (**self).point(x, y, flags)
35 }
36
37 fn end_contour(&mut self) {
38 (**self).end_contour()
39 }
40}
41
42#[derive(Copy, Clone)]
43enum PendingElement {
44 Line([f32; 2]),
45 Quad([f32; 4]),
46 Cubic([f32; 6]),
47}
48
49pub(crate) struct PointsFromPath<'a, S: ?Sized> {
57 sink: &'a mut S,
58 open: bool,
60 last_start: Option<(f32, f32)>,
61 pending: Option<PendingElement>,
62}
63
64impl<'a, S: ?Sized> PointsFromPath<'a, S> {
65 pub(crate) fn new(sink: &'a mut S) -> Self {
66 Self {
67 sink,
68 open: false,
69 last_start: None,
70 pending: None,
71 }
72 }
73}
74
75impl<S: UnscaledOutlineSink + ?Sized> PointsFromPath<'_, S> {
76 fn push(&mut self, x: f32, y: f32, flags: PointFlags) {
77 if !self.open {
78 self.sink.start_contour();
79 self.open = true;
80 }
81 self.sink.point(x as i32, y as i32, flags);
84 }
85
86 fn flush_pending(&mut self, for_close: bool) {
87 if let Some(element) = self.pending.take() {
88 let [x, y] = match element {
89 PendingElement::Line([x, y]) => [x, y],
90 PendingElement::Quad([x0, y0, x, y]) => {
91 self.push(x0, y0, PointFlags::off_curve_quad());
92 [x, y]
93 }
94 PendingElement::Cubic([x0, y0, x1, y1, x, y]) => {
95 self.push(x0, y0, PointFlags::off_curve_cubic());
96 self.push(x1, y1, PointFlags::off_curve_cubic());
97 [x, y]
98 }
99 };
100 if !for_close || self.last_start != Some((x, y)) {
101 self.push(x, y, PointFlags::on_curve());
102 }
103 }
104 }
105
106 fn close_contour(&mut self) {
107 self.flush_pending(true);
108 if self.open {
109 self.sink.end_contour();
110 self.open = false;
111 }
112 }
113
114 pub(crate) fn finish(mut self) {
116 self.close_contour();
117 }
118}
119
120impl<S: UnscaledOutlineSink + ?Sized> PathSink for PointsFromPath<'_, S> {
121 fn move_to(&mut self, x: f32, y: f32) {
122 self.close_contour();
123 self.push(x, y, PointFlags::on_curve());
124 self.last_start = Some((x, y));
125 }
126
127 fn line_to(&mut self, x: f32, y: f32) {
128 self.flush_pending(false);
129 self.pending = Some(PendingElement::Line([x, y]));
130 }
131
132 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
133 self.flush_pending(false);
134 self.pending = Some(PendingElement::Quad([cx0, cy0, x, y]));
135 }
136
137 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
138 self.flush_pending(false);
139 self.pending = Some(PendingElement::Cubic([cx0, cy0, cx1, cy1, x, y]));
140 }
141
142 fn close(&mut self) {
143 self.close_contour();
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::model::glyph::outline::DrawMemory;
151 use crate::{model::Font, types::GlyphId};
152 use alloc::vec::Vec;
153
154 #[derive(Default)]
156 struct Recorded {
157 points: Vec<(i32, i32, u8)>,
158 starts: Vec<usize>,
159 open: bool,
160 ended: usize,
161 }
162
163 impl UnscaledOutlineSink for Recorded {
164 fn start_contour(&mut self) {
165 assert!(!self.open, "a contour was started inside another");
166 self.open = true;
167 self.starts.push(self.points.len());
168 }
169
170 fn point(&mut self, x: i32, y: i32, flags: PointFlags) {
171 assert!(self.open, "a point arrived outside a contour");
172 self.points.push((x, y, flags.to_bits()));
173 }
174
175 fn end_contour(&mut self) {
176 assert!(self.open, "a contour ended that never started");
177 self.open = false;
178 self.ended += 1;
179 }
180 }
181
182 fn record(data: &[u8], glyph_id: u32) -> Recorded {
183 let font = Font::new(data.to_vec(), 0).unwrap();
184 let glyph = font.outline_glyphs().get(GlyphId::new(glyph_id)).unwrap();
185 let mut recorded = Recorded::default();
186 glyph
187 .draw_unscaled(DrawMemory::Scratch, &mut recorded)
188 .unwrap();
189 assert!(!recorded.open);
190 assert_eq!(recorded.ended, recorded.starts.len());
191 recorded
192 }
193
194 #[test]
195 fn read_glyf_outline() {
196 let recorded = record(font_test_data::MATERIAL_SYMBOLS_SUBSET, 5);
197 let expected = [
198 (400, 80, 1),
200 (400, 360, 1),
201 (320, 360, 1),
202 (320, 600, 1),
203 (320, 633, 0),
204 (367, 680, 0),
205 (400, 680, 1),
206 (560, 680, 1),
207 (593, 680, 0),
208 (640, 633, 0),
209 (640, 600, 1),
210 (640, 360, 1),
211 (560, 360, 1),
212 (560, 80, 1),
213 (480, 720, 1),
215 (447, 720, 0),
216 (400, 767, 0),
217 (400, 800, 1),
218 (400, 833, 0),
219 (447, 880, 0),
220 (480, 880, 1),
221 (513, 880, 0),
222 (560, 833, 0),
223 (560, 800, 1),
224 (560, 767, 0),
225 (513, 720, 0),
226 ];
227 assert_eq!(recorded.points, expected);
228 assert_eq!(recorded.starts, [0, 14]);
229 }
230
231 #[test]
232 fn the_advance_comes_with_the_points() {
233 let font = Font::new(font_test_data::MATERIAL_SYMBOLS_SUBSET.to_vec(), 0).unwrap();
234 let glyph = font.outline_glyphs().get(GlyphId::new(5)).unwrap();
235 let metrics = glyph
236 .draw_unscaled(DrawMemory::Scratch, &mut Recorded::default())
237 .unwrap();
238 assert_eq!(
239 metrics.h_advance,
240 Some(font.glyph_metrics().h_advance(GlyphId::new(5)))
241 );
242 }
243
244 #[test]
245 fn read_cubic_glyf_outline() {
246 let recorded = record(font_test_data::CUBIC_GLYF, 2);
247 let expected = [
248 (278, 710, 1),
249 (278, 470, 1),
250 (300, 500, 128),
251 (800, 500, 128),
252 (998, 470, 1),
253 (998, 710, 1),
254 ];
255 assert_eq!(recorded.points, expected);
256 assert_eq!(recorded.starts, [0]);
257 }
258
259 #[test]
260 fn read_cff_outline() {
261 let recorded = record(font_test_data::CANTARELL_VF_TRIMMED, 2);
262 let expected = [
263 (83, 0, 1),
265 (163, 0, 1),
266 (163, 482, 1),
267 (83, 482, 1),
268 (124, 595, 1),
270 (160, 595, 128),
271 (181, 616, 128),
272 (181, 652, 1),
273 (181, 688, 128),
274 (160, 709, 128),
275 (124, 709, 1),
276 (88, 709, 128),
277 (67, 688, 128),
278 (67, 652, 1),
279 (67, 616, 128),
280 (88, 595, 128),
281 ];
282 assert_eq!(recorded.points, expected);
283 assert_eq!(recorded.starts, [0, 4]);
284 }
285
286 #[test]
287 fn a_varc_composite_rebuilds_from_its_path() {
288 let font = Font::new(font_test_data::varc::CJK_6868.to_vec(), 0).unwrap();
289 let outlines = font.outline_glyphs();
290 let gid = (0..font.num_glyphs())
291 .map(GlyphId::new)
292 .find(|gid| {
293 matches!(
294 outlines.get(*gid),
295 Some(crate::model::glyph::outline::OutlineGlyph::Varc(_))
296 )
297 })
298 .unwrap();
299 let mut recorded = Recorded::default();
300 outlines
301 .get(gid)
302 .unwrap()
303 .draw_unscaled(DrawMemory::Scratch, &mut recorded)
304 .unwrap();
305 assert!(recorded.points.len() > 2);
306 assert_eq!(recorded.starts.first(), Some(&0));
307 assert!(!recorded.open);
308 }
309
310 #[test]
312 fn a_closing_quad_does_not_repeat_the_start() {
313 let mut recorded = Recorded::default();
314 let mut pen = PointsFromPath::new(&mut recorded);
315 pen.move_to(0.0, 0.0);
316 pen.line_to(10.0, 0.0);
317 pen.quad_to(10.0, 10.0, 0.0, 0.0);
318 pen.close();
319 pen.finish();
320 let on = PointFlags::on_curve().to_bits();
321 let off = PointFlags::off_curve_quad().to_bits();
322 assert_eq!(recorded.points, [(0, 0, on), (10, 0, on), (10, 10, off)]);
323 }
324
325 #[test]
328 fn varc_contours_do_not_repeat_their_start() {
329 let font = Font::new(font_test_data::varc::CJK_6868.to_vec(), 0).unwrap();
330 let outlines = font.outline_glyphs();
331 let mut contours = 0;
332 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
333 let Some(glyph @ crate::model::glyph::outline::OutlineGlyph::Varc(_)) =
334 outlines.get(gid)
335 else {
336 continue;
337 };
338 let mut recorded = Recorded::default();
339 glyph
340 .draw_unscaled(DrawMemory::Scratch, &mut recorded)
341 .unwrap();
342 let ends = recorded.starts[1..]
343 .iter()
344 .copied()
345 .chain([recorded.points.len()]);
346 for (start, end) in recorded.starts.iter().copied().zip(ends) {
347 let (first, last) = (recorded.points[start], recorded.points[end - 1]);
348 assert!(
349 end - start < 2 || (first.0, first.1) != (last.0, last.1),
350 "glyph {gid} contour at {start}"
351 );
352 contours += 1;
353 }
354 }
355 assert!(contours > 0);
356 }
357
358 #[test]
362 fn omit_unnecessary_trailing_oncurves() {
363 let mut recorded = Recorded::default();
364 let mut pen = PointsFromPath::new(&mut recorded);
365 pen.move_to(0.5, 1.5);
366 pen.line_to(1.0, 2.0);
367 pen.line_to(0.5, 1.5);
369 pen.close();
370 pen.move_to(5.0, 6.0);
371 pen.curve_to(1.0, 1.0, 2.0, 2.0, 2.0, 3.0);
372 pen.curve_to(1.0, 1.0, 2.0, 2.0, 5.0, 6.0);
374 pen.close();
375 pen.move_to(5.0, 6.0);
376 pen.curve_to(1.0, 1.0, 2.0, 2.0, 2.0, 3.0);
378 pen.finish();
380 let on_curves: Vec<_> = recorded
381 .points
382 .iter()
383 .map(|(_, _, flags)| PointFlags::from_bits(*flags).is_on_curve())
384 .collect();
385 #[rustfmt::skip]
386 let expected_on_curves = [
387 true, true, true, false, false, true, false, false, true, false, false, true, ];
402 assert_eq!(on_curves, expected_on_curves);
403 assert_eq!(recorded.starts, [0, 2, 8]);
404 assert_eq!(recorded.ended, 3);
405 }
406}