1use super::{
4 outline_to_path, Buffers, Hinter, OutlineContext, OutlinePlan, OutlineRef, PathContourStart,
5 Scale, Scale26Dot6, ScaleF32, Unscaled,
6};
7use crate::model::Error;
8use crate::{
9 model::glyph::outline::PathSink,
10 tables::glyf::{PointFlags, PHANTOM_POINT_COUNT},
11 types::{F26Dot6, GlyphId, Point},
12};
13use alloc::vec::Vec;
14
15pub struct Outline<S: Scale> {
49 points: Vec<Point<S::Coord>>,
50 flags: Vec<PointFlags>,
51 contours: Vec<u16>,
52 phantom: [Point<S::Coord>; PHANTOM_POINT_COUNT],
53 unscaled: Vec<Point<i32>>,
54 deltas: Vec<Point<S::Delta>>,
55 iup: Vec<Point<S::Delta>>,
56 composite_deltas: Vec<Point<S::Delta>>,
57 point_count: u32,
58 contour_count: u32,
59}
60
61impl<S: Scale> Outline<S> {
62 pub fn new() -> Self {
64 Self {
65 points: Vec::new(),
66 flags: Vec::new(),
67 contours: Vec::new(),
68 phantom: Default::default(),
69 unscaled: Vec::new(),
70 deltas: Vec::new(),
71 iup: Vec::new(),
72 composite_deltas: Vec::new(),
73 point_count: 0,
74 contour_count: 0,
75 }
76 }
77
78 pub fn points(&self) -> &[Point<S::Coord>] {
80 &self.points[..self.point_count as usize]
81 }
82
83 pub fn flags(&self) -> &[PointFlags] {
85 &self.flags[..self.point_count as usize]
86 }
87
88 pub fn contours(&self) -> &[u16] {
90 &self.contours[..self.contour_count as usize]
91 }
92
93 pub fn phantom_points(&self) -> &[Point<S::Coord>; PHANTOM_POINT_COUNT] {
95 &self.phantom
96 }
97
98 pub fn adjusted_lsb(&self) -> S::Coord {
100 self.phantom[0].x
101 }
102
103 pub fn adjusted_advance_width(&self) -> S::Coord {
105 self.phantom[1].x - self.phantom[0].x
106 }
107
108 #[must_use]
113 pub fn to_path(&self, start: PathContourStart, sink: &mut impl PathSink) -> bool {
114 outline_to_path(self.points(), self.flags(), self.contours(), start, sink)
115 }
116
117 pub fn load_with<'a>(
122 &mut self,
123 context: &'a dyn OutlineContext<'a>,
124 glyph: GlyphId,
125 scale: &S,
126 hinter: Option<&mut dyn Hinter<S::Coord>>,
127 ) -> Result<OutlineRef<'_, S::Coord>, Error> {
128 let plan = OutlinePlan::new(context, glyph)?;
129 let lengths = plan.buffer_lengths::<S>();
130 grow(&mut self.points, lengths.points);
131 grow(&mut self.flags, lengths.points);
132 grow(&mut self.contours, lengths.contours);
133 grow(&mut self.unscaled, lengths.unscaled);
134 grow(&mut self.deltas, lengths.deltas);
135 grow(&mut self.iup, lengths.iup);
136 grow(&mut self.composite_deltas, lengths.composite_deltas);
137 let buffers = Buffers {
138 points: &mut self.points,
139 flags: &mut self.flags,
140 contours: &mut self.contours,
141 unscaled: &mut self.unscaled,
142 deltas: &mut self.deltas,
143 iup: &mut self.iup,
144 composite_deltas: &mut self.composite_deltas,
145 };
146 let outline = plan.load(context, scale, buffers, hinter)?;
147 self.point_count = outline.points().len() as u32;
149 self.contour_count = outline.contours().len() as u32;
150 self.phantom = *outline.phantom_points();
151 Ok(outline)
152 }
153}
154
155impl<S: Scale> Default for Outline<S> {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161impl Outline<Scale26Dot6> {
162 pub fn load<'a>(
167 &mut self,
168 context: &'a dyn OutlineContext<'a>,
169 glyph: GlyphId,
170 ppem: Option<f32>,
171 ) -> Result<OutlineRef<'_, F26Dot6>, Error> {
172 let scale = Scale26Dot6::new(ppem, context.units_per_em());
173 self.load_with(context, glyph, &scale, None)
174 }
175
176 pub fn load_hinted<'a>(
179 &mut self,
180 context: &'a dyn OutlineContext<'a>,
181 glyph: GlyphId,
182 ppem: Option<f32>,
183 hinter: &mut dyn Hinter<F26Dot6>,
184 ) -> Result<OutlineRef<'_, F26Dot6>, Error> {
185 let scale = Scale26Dot6::new(ppem, context.units_per_em());
186 self.load_with(context, glyph, &scale, Some(hinter))
187 }
188}
189
190impl Outline<ScaleF32> {
191 pub fn load<'a>(
196 &mut self,
197 context: &'a dyn OutlineContext<'a>,
198 glyph: GlyphId,
199 ppem: Option<f32>,
200 ) -> Result<OutlineRef<'_, f32>, Error> {
201 let scale = ScaleF32::new(ppem, context.units_per_em());
202 self.load_with(context, glyph, &scale, None)
203 }
204}
205
206impl Outline<Unscaled> {
207 pub fn load<'a>(
212 &mut self,
213 context: &'a dyn OutlineContext<'a>,
214 glyph: GlyphId,
215 ) -> Result<OutlineRef<'_, i32>, Error> {
216 self.load_with(context, glyph, &Unscaled, None)
217 }
218}
219
220fn grow<T: Copy + Default>(buf: &mut Vec<T>, len: u32) {
222 let len = len as usize;
223 if buf.len() < len {
224 buf.resize(len, T::default());
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::super::OutlineTables;
231 use super::*;
232 use crate::{types::F2Dot14, FontRef, TableProvider};
233 use alloc::{vec, vec::Vec};
234
235 #[test]
237 fn matches_the_low_level_api() {
238 let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
239 let context = OutlineTables::new(&font).unwrap();
240 let scale = Scale26Dot6::new(Some(16.0), context.units_per_em);
241 let mut owned = Outline::<Scale26Dot6>::new();
242 for gid in 0..font.maxp().unwrap().num_glyphs() {
243 let gid = GlyphId::from(gid);
244 let plan = OutlinePlan::new(&context, gid).unwrap();
245 let lengths = plan.buffer_lengths::<Scale26Dot6>();
246 let mut points = vec![Point::default(); lengths.points as usize];
247 let mut flags = vec![PointFlags::default(); lengths.points as usize];
248 let mut contours = vec![0u16; lengths.contours as usize];
249 let mut unscaled = vec![Point::default(); lengths.unscaled as usize];
250 let buffers = Buffers {
251 points: &mut points,
252 flags: &mut flags,
253 contours: &mut contours,
254 unscaled: &mut unscaled,
255 deltas: &mut [],
256 iup: &mut [],
257 composite_deltas: &mut [],
258 };
259 let expected = plan.load(&context, &scale, buffers, None).unwrap();
260 let expected = (
261 expected.points().to_vec(),
262 expected.contours().to_vec(),
263 expected.phantom_points(),
264 );
265 let actual = owned.load(&context, gid, Some(16.0)).unwrap();
266 assert_eq!(actual.points(), expected.0, "gid {gid}");
267 assert_eq!(actual.contours(), expected.1, "gid {gid}");
268 assert_eq!(actual.phantom_points(), expected.2, "gid {gid}");
269 }
270 }
271
272 #[test]
274 fn accessors_match_the_returned_outline() {
275 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
276 let context = OutlineTables::new(&font).unwrap();
277 let mut owned = Outline::<Scale26Dot6>::new();
278 let (points, contours, phantom, lsb, advance) = {
279 let out = owned.load(&context, GlyphId::new(5), None).unwrap();
280 (
281 out.points().to_vec(),
282 out.contours().to_vec(),
283 *out.phantom_points(),
284 out.adjusted_lsb(),
285 out.adjusted_advance_width(),
286 )
287 };
288 assert!(!points.is_empty());
289 assert_eq!(owned.points(), points);
290 assert_eq!(owned.contours(), contours);
291 assert_eq!(owned.phantom_points(), &phantom);
292 assert_eq!(owned.adjusted_lsb(), lsb);
293 assert_eq!(owned.adjusted_advance_width(), advance);
294 }
295
296 #[test]
299 fn buffers_grow_and_are_reused() {
300 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
301 let context = OutlineTables::new(&font).unwrap();
302 let mut owned = Outline::<Scale26Dot6>::new();
303 owned.load(&context, GlyphId::new(5), None).unwrap();
305 let big = owned.points().len();
306 let capacity = owned.points.len();
307 owned.load(&context, GlyphId::new(1), None).unwrap();
308 assert!(owned.points().len() < big);
309 assert_eq!(owned.points.len(), capacity, "buffers must not shrink");
310 assert_eq!(owned.points().len(), owned.flags().len());
311 }
312
313 #[test]
315 fn reuse_does_not_leak_between_glyphs() {
316 let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
317 let context = OutlineTables::new(&font).unwrap();
318 let mut owned = Outline::<Scale26Dot6>::new();
319 let first = owned
320 .load(&context, GlyphId::new(1), Some(24.0))
321 .unwrap()
322 .points
323 .to_vec();
324 owned.load(&context, GlyphId::new(3), Some(24.0)).unwrap();
326 let again = owned.load(&context, GlyphId::new(1), Some(24.0)).unwrap();
327 assert_eq!(again.points(), first);
328 }
329
330 #[test]
332 fn unscaled_agrees_with_26dot6_at_no_size() {
333 let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
334 let context = OutlineTables::new(&font).unwrap();
335 let mut fixed = Outline::<Scale26Dot6>::new();
336 let mut raw = Outline::<Unscaled>::new();
337 for gid in 0..font.maxp().unwrap().num_glyphs() {
338 let gid = GlyphId::from(gid);
339 let expected: Vec<_> = fixed
340 .load(&context, gid, None)
341 .unwrap()
342 .points
343 .iter()
344 .map(|point| point.map(|coord| coord.to_bits() >> 6))
345 .collect();
346 let actual = raw.load(&context, gid).unwrap();
347 assert_eq!(actual.points(), expected, "gid {gid}");
348 }
349 }
350
351 #[test]
353 fn f32_tracks_26dot6() {
354 let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
355 let context = OutlineTables::new(&font).unwrap();
356 let mut fixed = Outline::<Scale26Dot6>::new();
357 let mut float = Outline::<ScaleF32>::new();
358 for gid in 0..font.maxp().unwrap().num_glyphs() {
359 let gid = GlyphId::from(gid);
360 let expected = fixed
361 .load(&context, gid, Some(32.0))
362 .unwrap()
363 .points
364 .to_vec();
365 let actual = float.load(&context, gid, Some(32.0)).unwrap();
366 for (expected, actual) in expected.iter().zip(actual.points()) {
367 assert!(
368 (expected.x.to_f32() - actual.x).abs() < 1.0 / 64.0,
369 "gid {gid}"
370 );
371 assert!(
372 (expected.y.to_f32() - actual.y).abs() < 1.0 / 64.0,
373 "gid {gid}"
374 );
375 }
376 }
377 }
378
379 #[test]
381 fn coords_vary_the_outline() {
382 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
383 let context = OutlineTables::new(&font).unwrap();
384 assert!(context.gvar.is_some());
385 let mut owned = Outline::<Scale26Dot6>::new();
386 let default = owned
387 .load(&context, GlyphId::new(2), Some(16.0))
388 .unwrap()
389 .points
390 .to_vec();
391 let coords = [F2Dot14::from_f32(1.0)];
392 let context = context.at(&coords, &[]);
393 let varied = owned.load(&context, GlyphId::new(2), Some(16.0)).unwrap();
394 assert_eq!(varied.points().len(), default.len());
395 assert_ne!(varied.points(), default);
396 }
397
398 #[test]
401 fn tables_supply_phantom_metrics() {
402 let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
403 let context = OutlineTables::new(&font).unwrap();
404 let hmtx = font.hmtx().unwrap();
405 let mut owned = Outline::<Unscaled>::new();
406 for gid in 0..font.maxp().unwrap().num_glyphs() {
407 let gid = GlyphId::from(gid);
408 let out = owned.load(&context, gid).unwrap();
409 assert_eq!(
410 out.adjusted_advance_width(),
411 hmtx.advance(gid).unwrap() as i32,
412 "gid {gid}"
413 );
414 }
415 }
416}