Skip to main content

read_fonts/tables/glyf/outline/
load.rs

1//! The outline loading pass.
2//!
3//! Walks a glyph and the composite tree below it, filling the caller's buffers
4//! with points, flags and contour end points. The arithmetic belongs to a
5//! [`Scale`]; the structure lives here and is shared by every scale.
6
7use super::{
8    scale::{OffsetRounding, Scale, SimplePass},
9    Buffers, GlyphZone, Hinter, OutlineContext, OutlinePlan, OutlineRef,
10};
11use crate::model::Error;
12use crate::{
13    limits::MAX_RECURSION_DEPTH,
14    tables::{
15        glyf::{
16            Anchor, CompositeGlyph, CompositeGlyphFlags, Glyph, PointMarker, SimpleGlyph,
17            PHANTOM_POINT_COUNT,
18        },
19        gvar::DeltaBuffers,
20    },
21    types::{F2Dot14, Fixed, GlyphId, Point},
22};
23
24/// Builds the outline a plan describes.
25///
26/// The method on [`OutlinePlan`] is the entry point; this is where the work
27/// lives, alongside the pass that does it.
28pub(super) fn load<'a, 's, 'buf, S: Scale>(
29    plan: &'s OutlinePlan<'a>,
30    context: &'a dyn OutlineContext<'a>,
31    scale: &'s S,
32    buffers: Buffers<'buf, S>,
33    hinter: Option<&mut dyn Hinter<S::Coord>>,
34) -> Result<OutlineRef<'buf, S::Coord>, Error> {
35    let coords = context.coords();
36    let varies = plan.applies_variations;
37    let is_hinted = hinter.is_some() && scale.is_scaled();
38    let mut pass = Pass {
39        context,
40        scale,
41        varies,
42        coords,
43        gvar_scalars: context.gvar_scalars(),
44        has_hvar: context.has_hvar(),
45        buffers,
46        point_count: 0,
47        contour_count: 0,
48        component_delta_count: 0,
49        phantom: Default::default(),
50        hinter,
51        is_hinted,
52    };
53    pass.load(&plan.glyph_data, plan.glyph, 0)?;
54    Ok(pass.finish())
55}
56
57/// Slicing that reports a caller buffer sized too small, rather than panicking
58/// or silently truncating.
59trait TrySlice<T> {
60    fn try_mut<I>(&mut self, index: I) -> Result<&mut [T], Error>
61    where
62        I: core::slice::SliceIndex<[T], Output = [T]>;
63
64    fn try_ref<I>(&self, index: I) -> Result<&[T], Error>
65    where
66        I: core::slice::SliceIndex<[T], Output = [T]>;
67}
68
69impl<T> TrySlice<T> for [T] {
70    fn try_mut<I>(&mut self, index: I) -> Result<&mut [T], Error>
71    where
72        I: core::slice::SliceIndex<[T], Output = [T]>,
73    {
74        self.get_mut(index).ok_or(Error::InsufficientMemory)
75    }
76
77    fn try_ref<I>(&self, index: I) -> Result<&[T], Error>
78    where
79        I: core::slice::SliceIndex<[T], Output = [T]>,
80    {
81        self.get(index).ok_or(Error::InsufficientMemory)
82    }
83}
84
85struct Pass<'a, 's, 'buf, 'hint, S: Scale> {
86    context: &'a dyn OutlineContext<'a>,
87    scale: &'s S,
88    varies: bool,
89    coords: &'s [F2Dot14],
90    gvar_scalars: &'s [Fixed],
91    has_hvar: bool,
92    buffers: Buffers<'buf, S>,
93    point_count: usize,
94    contour_count: usize,
95    component_delta_count: usize,
96    phantom: [Point<S::Coord>; PHANTOM_POINT_COUNT],
97    hinter: Option<&'hint mut dyn Hinter<S::Coord>>,
98    is_hinted: bool,
99}
100
101impl<'a, 's, 'buf, S: Scale> Pass<'a, 's, 'buf, '_, S> {
102    fn finish(self) -> OutlineRef<'buf, S::Coord> {
103        let Self {
104            buffers,
105            point_count,
106            contour_count,
107            phantom,
108            ..
109        } = self;
110        let points = &mut buffers.points[..point_count];
111        // The outline is positioned relative to the left side bearing.
112        let x_shift = phantom[0].x;
113        if x_shift != S::Coord::default() {
114            for point in points.iter_mut() {
115                point.x = point.x - x_shift;
116            }
117        }
118        OutlineRef {
119            points,
120            flags: &buffers.flags[..point_count],
121            contours: &buffers.contours[..contour_count],
122            phantom,
123        }
124    }
125
126    fn load(
127        &mut self,
128        glyph: &Option<Glyph<'a>>,
129        gid: GlyphId,
130        recurse_depth: usize,
131    ) -> Result<(), Error> {
132        // `OutlinePlan::new` checks this too, but it is a separate argument
133        // to `load`, so nothing makes the tree walked here the one surveyed.
134        if recurse_depth > MAX_RECURSION_DEPTH {
135            return Err(Error::LimitExceeded);
136        }
137        self.setup_phantom_points(glyph, gid);
138        match glyph {
139            Some(Glyph::Simple(simple)) => self.load_simple(simple, gid),
140            Some(Glyph::Composite(composite)) => self.load_composite(composite, gid, recurse_depth),
141            None => self.load_empty(gid),
142        }
143    }
144
145    /// Sets up the four points appended to every outline that carry its
146    /// metrics.
147    ///
148    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructing_glyphs#phantom-points>
149    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttgload.c#L1365>
150    fn setup_phantom_points(&mut self, glyph: &Option<Glyph<'a>>, gid: GlyphId) {
151        let (x_min, y_max) = match glyph {
152            Some(glyph) => (glyph.x_min() as i32, glyph.y_max() as i32),
153            None => (0, 0),
154        };
155        let (lsb, advance) = self.context.h_metrics(gid);
156        let (tsb, vadvance) = self.context.v_metrics(gid).unwrap_or_else(|| {
157            let (ascent, descent) = self.context.h_line_metrics();
158            (ascent - y_max, ascent - descent)
159        });
160        let from = |value| self.scale.phantom_from_font_units(value);
161        // horizontal:
162        self.phantom[0] = Point::new(from(x_min - lsb), from(0));
163        self.phantom[1] = Point::new(from(x_min - lsb + advance), from(0));
164        // vertical:
165        self.phantom[2] = Point::new(from(0), from(y_max + tsb));
166        self.phantom[3] = Point::new(from(0), from(y_max + tsb - vadvance));
167    }
168
169    fn load_empty(&mut self, gid: GlyphId) -> Result<(), Error> {
170        let deltas = if self.varies {
171            self.context.phantom_point_deltas(gid)
172        } else {
173            None
174        };
175        self.scale.load_empty_phantom(&mut self.phantom, deltas);
176        Ok(())
177    }
178
179    /// Copies a glyph's contour end points into the buffer, checking that they
180    /// ascend, and returns the range they occupy.
181    ///
182    /// They stay relative to the glyph until its points have been loaded; see
183    /// the end of [`Self::load_simple`].
184    fn read_contours(&mut self, glyph: &SimpleGlyph<'a>) -> Result<core::ops::Range<usize>, Error> {
185        let end_pts = glyph.end_pts_of_contours();
186        let range = self.contour_count..self.contour_count + end_pts.len();
187        let contours = self.buffers.contours.try_mut(range.clone())?;
188        let mut previous = 0;
189        for (end_pt, contour) in end_pts.iter().zip(contours.iter_mut()) {
190            let end_pt = end_pt.get();
191            if end_pt < previous {
192                // Unordered contour end points.
193                return Err(Error::Malformed);
194            }
195            previous = end_pt;
196            *contour = end_pt;
197        }
198        Ok(range)
199    }
200
201    fn load_simple(&mut self, glyph: &SimpleGlyph<'a>, gid: GlyphId) -> Result<(), Error> {
202        let points_start = self.point_count;
203        let point_count = glyph.num_points();
204        let with_phantom = point_count + PHANTOM_POINT_COUNT;
205        let point_range = points_start..points_start + with_phantom;
206
207        // Contour end points come first, so the delta pass can see them.
208        let contours_range = self.read_contours(glyph)?;
209
210        // Copied out so that the buffer borrows below do not conflict with
211        // reading the rest of `self`.
212        // Looked up here rather than held on the pass: a composite's
213        // components each have their own variation data.
214        let var_data = if self.varies {
215            self.context.glyph_variation_data(gid)
216        } else {
217            None
218        };
219        let (is_hinted, phantom, coords, has_hvar, scale) = (
220            self.is_hinted,
221            self.phantom,
222            self.coords,
223            self.has_hvar,
224            self.scale,
225        );
226
227        let scaled = self.buffers.points.try_mut(point_range.clone())?;
228        let flags = self.buffers.flags.try_mut(point_range)?;
229        let unscaled = if S::NEEDS_UNSCALED {
230            self.buffers.unscaled.try_mut(..with_phantom)?
231        } else {
232            &mut []
233        };
234        let deltas = if self.varies {
235            DeltaBuffers {
236                deltas: self.buffers.deltas.try_mut(..with_phantom)?,
237                iup: self.buffers.iup.try_mut(..with_phantom)?,
238            }
239        } else {
240            DeltaBuffers {
241                deltas: &mut [],
242                iup: &mut [],
243            }
244        };
245        let contours = self.buffers.contours.try_ref(contours_range.clone())?;
246
247        self.phantom = scale.load_simple_points(SimplePass {
248            glyph,
249            scaled,
250            unscaled,
251            flags,
252            contours,
253            phantom,
254            deltas,
255            var_data,
256            varies: self.varies,
257            coords,
258            gvar_scalars: self.gvar_scalars,
259            has_hvar,
260            is_hinted,
261        })?;
262
263        self.point_count += point_count;
264        self.contour_count += contours_range.len();
265
266        if self.is_hinted {
267            self.hint_glyph(
268                glyph.instructions(),
269                points_start,
270                contours_range.clone(),
271                point_count,
272                gid,
273                false,
274            )?;
275        }
276
277        if points_start != 0 {
278            // Contour end points are read relative to this glyph, so shift
279            // them into the outline's coordinate space.
280            let contours = &mut self.buffers.contours[contours_range];
281            for contour_end in contours.iter_mut() {
282                *contour_end += points_start as u16;
283            }
284        }
285        Ok(())
286    }
287
288    /// Runs the hinter over the points just written, if there are instructions
289    /// and hinting is active.
290    ///
291    /// `points_start` is where this glyph's points begin in the outline, and
292    /// `phantom_offset` is where its phantom points begin within them.
293    fn hint_glyph(
294        &mut self,
295        bytecode: &'a [u8],
296        points_start: usize,
297        contours_range: core::ops::Range<usize>,
298        phantom_offset: usize,
299        gid: GlyphId,
300        is_composite: bool,
301    ) -> Result<(), Error> {
302        if !self.is_hinted {
303            return Ok(());
304        }
305        let Some(hinter) = self.hinter.as_deref_mut() else {
306            return Ok(());
307        };
308        let point_range = points_start..self.point_count + PHANTOM_POINT_COUNT;
309        if bytecode.is_empty() {
310            // Even without instructions FreeType uses rounded phantom points
311            // when hinting is requested and backward compatibility is off.
312            // It never does this for composites.
313            if !is_composite && !hinter.backward_compatibility() {
314                let scaled = &self.buffers.points[point_range.clone()];
315                for (scaled, phantom) in scaled[phantom_offset..].iter().zip(&mut self.phantom) {
316                    *phantom = self.scale.round_phantom(*scaled);
317                }
318            }
319            return Ok(());
320        }
321        let scaled = self.buffers.points.try_mut(point_range.clone())?;
322        let flags = self.buffers.flags.try_mut(point_range)?;
323        let unscaled = self.buffers.unscaled.try_mut(..scaled.len())?;
324        if is_composite {
325            // A composite has no font unit points of its own, so the hinter
326            // gets a copy of the loaded ones. Its phantom points are
327            // appended here too, and the touched markers left by the
328            // components are cleared.
329            for (i, phantom) in self.phantom.iter().enumerate() {
330                scaled[phantom_offset + i] = *phantom;
331                flags[phantom_offset + i] = Default::default();
332            }
333            for (unscaled, scaled) in unscaled.iter_mut().zip(scaled.iter()) {
334                *unscaled = Point::new(
335                    self.scale.coord_to_hinting_raw(scaled.x),
336                    self.scale.coord_to_hinting_raw(scaled.y),
337                );
338            }
339            for flag in flags.iter_mut() {
340                flag.clear_marker(PointMarker::TOUCHED);
341            }
342        }
343        // Hinting rounds the phantom points.
344        for point in &mut scaled[phantom_offset..] {
345            *point = self.scale.round_phantom(*point);
346        }
347        // The hinter reads contour end points relative to the glyph it is
348        // given, but they are stored relative to the whole outline. Rebase them
349        // for the call and put them back afterwards. Only a composite can start
350        // anywhere but zero.
351        let rebase = if is_composite { points_start as u16 } else { 0 };
352        if rebase != 0 {
353            for contour in self.buffers.contours.try_mut(contours_range.clone())? {
354                *contour -= rebase;
355            }
356        }
357        let result = {
358            let contours = self.buffers.contours.try_ref(contours_range.clone())?;
359            let mut zone = GlyphZone {
360                glyph: gid,
361                bytecode,
362                points: scaled,
363                unscaled,
364                flags,
365                contours,
366                phantom: &mut self.phantom,
367                is_composite,
368                coords: self.coords,
369            };
370            hinter.hint(&mut zone)
371        };
372        if rebase != 0 {
373            for contour in self.buffers.contours.try_mut(contours_range)? {
374                *contour += rebase;
375            }
376        }
377        Ok(result?)
378    }
379
380    fn load_composite(
381        &mut self,
382        glyph: &CompositeGlyph<'a>,
383        gid: GlyphId,
384        recurse_depth: usize,
385    ) -> Result<(), Error> {
386        let point_base = self.point_count;
387        let contour_base = self.contour_count;
388        let delta_base = self.component_delta_count;
389        let mut have_deltas = false;
390        // Instructions found while counting the components, so a varied
391        // glyph parses its component list once.
392        let mut counted_instructions = None;
393        let var_data = if self.varies {
394            self.context.glyph_variation_data(gid)
395        } else {
396            None
397        };
398        if let Some(var_data) = var_data {
399            let (count, instructions) = glyph.count_and_instructions();
400            counted_instructions = Some(instructions);
401            let count = count + PHANTOM_POINT_COUNT;
402            let deltas = self
403                .buffers
404                .composite_deltas
405                .try_mut(delta_base..delta_base + count)?;
406            if var_data
407                .composite_deltas_with_scalars(self.coords, self.gvar_scalars, deltas)
408                .is_some()
409            {
410                for (phantom, delta) in self
411                    .phantom
412                    .iter_mut()
413                    .zip(&deltas[count - PHANTOM_POINT_COUNT..])
414                {
415                    *phantom = self.scale.add_phantom_delta(*phantom, *delta);
416                }
417                have_deltas = true;
418            }
419            self.component_delta_count += count;
420        }
421        for point in self.phantom.iter_mut() {
422            *point = self.scale.scale_phantom(*point);
423        }
424        for (i, component) in glyph.components().enumerate() {
425            // Loading a component overwrites the phantom points, so save them
426            // and restore unless the component says to keep its own.
427            let phantom = self.phantom;
428            let start_point = self.point_count;
429            let component_glyph = self
430                .context
431                .glyph(component.glyph)
432                .ok_or(Error::MissingResource)?
433                .into_glyph();
434            self.load(&component_glyph, component.glyph, recurse_depth + 1)?;
435            let end_point = self.point_count;
436            if !component
437                .flags
438                .contains(CompositeGlyphFlags::USE_MY_METRICS)
439            {
440                self.phantom = phantom;
441            }
442            let have_xform = component.flags.intersects(
443                CompositeGlyphFlags::WE_HAVE_A_SCALE
444                    | CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE
445                    | CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO,
446            );
447            if have_xform {
448                let points = &mut self.buffers.points[start_point..end_point];
449                self.scale.transform_points(points, &component.transform);
450            }
451            let offset = match component.anchor {
452                Anchor::Offset { x, y } => {
453                    let scale_offset = have_xform
454                        && component.flags
455                            & (CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
456                                | CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET)
457                            == CompositeGlyphFlags::SCALED_COMPONENT_OFFSET;
458                    let delta = have_deltas
459                        .then(|| self.buffers.composite_deltas.get(delta_base + i).copied())
460                        .flatten();
461                    // Only a hinted outline lands on the grid, and the hinter
462                    // decides whether both axes do.
463                    let rounding = if self.is_hinted
464                        && component
465                            .flags
466                            .contains(CompositeGlyphFlags::ROUND_XY_TO_GRID)
467                    {
468                        OffsetRounding {
469                            x: self
470                                .hinter
471                                .as_deref()
472                                .is_some_and(|hinter| hinter.round_composite_x_offset()),
473                            y: true,
474                        }
475                    } else {
476                        OffsetRounding::default()
477                    };
478                    self.scale.component_offset(
479                        Point::new(x as i32, y as i32),
480                        &component.transform,
481                        scale_offset,
482                        delta,
483                        rounding,
484                    )
485                }
486                Anchor::Point { base, component } => {
487                    let base_point = *self
488                        .buffers
489                        .points
490                        .get(point_base + base as usize)
491                        .ok_or(Error::Malformed)?;
492                    let component_point = *self
493                        .buffers
494                        .points
495                        .get(start_point + component as usize)
496                        .ok_or(Error::Malformed)?;
497                    base_point - component_point
498                }
499            };
500            if offset.x != S::Coord::default() || offset.y != S::Coord::default() {
501                for point in &mut self.buffers.points[start_point..end_point] {
502                    *point += offset;
503                }
504            }
505        }
506        // Unconditional: the reservation above happens whenever this glyph has
507        // variation data, but `have_deltas` is only set when reading it
508        // succeeded. Guarding the restore leaks the reservation on malformed
509        // data, and a later composite then starts from a higher base.
510        self.component_delta_count = delta_base;
511        // The hook decides whether there is anything to do for a composite
512        // with no instructions.
513        if self.is_hinted {
514            let instructions = counted_instructions.unwrap_or_else(|| glyph.instructions());
515            self.hint_glyph(
516                instructions.unwrap_or_default(),
517                point_base,
518                contour_base..self.contour_count,
519                self.point_count - point_base,
520                gid,
521                true,
522            )?;
523        }
524        Ok(())
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use super::super::testing::build;
531    use super::*;
532    use crate::{
533        tables::glyf::outline::{Outline, OutlineTables, Scale26Dot6, Unscaled},
534        tables::glyf::PointFlags,
535        tables::{glyf::Glyf, gvar::Gvar, loca::Loca},
536        types::{F26Dot6, Fixed},
537        FontData, FontRead, FontRef,
538    };
539    use alloc::{vec, vec::Vec};
540
541    /// A composite whose variation data will not parse must not leak the
542    /// delta stack reservation onto its siblings.
543    ///
544    /// `load_composite` reserves space on the composite delta stack whenever
545    /// the glyph has variation data, but only sets `have_deltas` when reading
546    /// that data succeeded. Restoring the stack pointer only in that case left
547    /// the reservation in place for a malformed glyph, so the next composite
548    /// at the same level started from a higher base and could run the buffer
549    /// out. The restore is unconditional now.
550    #[test]
551    fn a_malformed_composite_does_not_leak_the_delta_stack() {
552        // Two contours' worth of nothing, just to have a simple glyph.
553        fn empty_simple() -> Vec<u8> {
554            let mut out = vec![];
555            out.extend_from_slice(&0i16.to_be_bytes()); // numberOfContours
556            out.extend_from_slice(&[0u8; 8]); // bounding box
557            out.extend_from_slice(&0u16.to_be_bytes()); // instructionLength
558            out
559        }
560        fn composite(components: &[u16]) -> Vec<u8> {
561            let mut out = vec![];
562            out.extend_from_slice(&(-1i16).to_be_bytes()); // numberOfContours
563            out.extend_from_slice(&[0u8; 8]); // bounding box
564            for (i, gid) in components.iter().enumerate() {
565                let last = i == components.len() - 1;
566                // ARG_1_AND_2_ARE_WORDS | ARGS_ARE_XY_VALUES, MORE_COMPONENTS
567                let flags = 0x0001u16 | 0x0002 | if last { 0 } else { 0x0020 };
568                out.extend_from_slice(&flags.to_be_bytes());
569                out.extend_from_slice(&gid.to_be_bytes());
570                out.extend_from_slice(&0i16.to_be_bytes()); // dx
571                out.extend_from_slice(&0i16.to_be_bytes()); // dy
572            }
573            out
574        }
575        fn tables_from_glyphs(glyphs: &[Vec<u8>]) -> (Vec<u8>, Vec<u8>) {
576            let mut glyf = vec![];
577            let mut loca = vec![];
578            for glyph in glyphs {
579                loca.extend_from_slice(&(glyf.len() as u32).to_be_bytes());
580                glyf.extend_from_slice(glyph);
581            }
582            loca.extend_from_slice(&(glyf.len() as u32).to_be_bytes());
583            (glyf, loca)
584        }
585        // gid 0 references two composites, each of which references the simple
586        // glyph. Only gid 1 carries variation data, and it is malformed.
587        let (glyf, loca) = tables_from_glyphs(&[
588            composite(&[1, 2]),
589            composite(&[3]),
590            composite(&[3]),
591            empty_simple(),
592        ]);
593        let glyf = Glyf::read(FontData::new(&glyf)).unwrap();
594        let loca = Loca::read(FontData::new(&loca), true).unwrap();
595        let gvar_bytes = malformed_gvar_for_composites();
596        let gvar = Gvar::read(FontData::new(&gvar_bytes)).unwrap();
597        // Reading has to fail for each of them, or this proves nothing.
598        for gid in 0..3 {
599            assert!(
600                gvar.glyph_variation_data(GlyphId::new(gid))
601                    .unwrap()
602                    .unwrap()
603                    .composite_deltas(
604                        &[F2Dot14::from_f32(1.0)],
605                        &mut [Point::<Fixed>::default(); 8]
606                    )
607                    .is_none(),
608                "gid {gid} parsed when it should not"
609            );
610        }
611        let coords = [F2Dot14::from_f32(1.0)];
612        let context = OutlineTables {
613            glyf,
614            loca,
615            gvar: Some(crate::tables::gvar::SelectedGvar::Original(gvar)),
616            hmtx: None,
617            os2: None,
618            hvar: None,
619            units_per_em: 1000,
620            coords: &coords,
621            gvar_scalars: &[],
622        };
623        let mut outline = Outline::<Unscaled>::new();
624        // Before the fix this returned `InsufficientMemory`: gid 2 started
625        // from the base gid 1 failed to give back.
626        assert!(outline.load(&context, GlyphId::new(0)).is_ok());
627    }
628
629    /// A `gvar` giving gids 0, 1 and 2 a tuple whose packed deltas are
630    /// truncated, so reading any of them fails.
631    fn malformed_gvar_for_composites() -> Vec<u8> {
632        use font_test_data::bebuffer::BeBuffer;
633        // One glyph's worth of variation data: a single tuple that claims
634        // deltas for every point and then supplies none.
635        let mut g = BeBuffer::new();
636        g = g.push(1u16); // tupleVariationCount
637        g = g.push(10u16); // dataOffset
638        g = g.push(2u16); // variationDataSize
639        g = g.push(0xA000u16); // EMBEDDED_PEAK_TUPLE | PRIVATE_POINT_NUMBERS
640        g = g.push(0x4000u16); // peak of 1.0 on the only axis
641        g = g.push(0u8); // private point numbers: zero means every point
642        g = g.push(0x3Fu8); // a run of 64 byte deltas, with none following
643        let blob = g.to_vec();
644        let len = blob.len() as u32;
645
646        let mut buf = BeBuffer::new();
647        buf = buf.push(1u16).push(0u16); // version 1.0
648        buf = buf.push(1u16); // axisCount
649                              // A shared tuple count of zero still needs a non-null offset.
650        buf = buf.push(0u16).push(20u32);
651        buf = buf.push(4u16); // glyphCount
652        buf = buf.push(1u16); // flags: 32 bit offsets
653        buf = buf.push(0u32); // glyphVariationDataArrayOffset, patched below
654                              // Gids 0, 1 and 2 each get a copy; gid 3 is the simple glyph.
655        buf = buf
656            .push(0u32)
657            .push(len)
658            .push(len * 2)
659            .push(len * 3)
660            .push(len * 3);
661        let mut bytes = buf.to_vec();
662        let array_start = bytes.len() as u32;
663        bytes[16..20].copy_from_slice(&array_start.to_be_bytes());
664        for _ in 0..3 {
665            bytes.extend_from_slice(&blob);
666        }
667        bytes
668    }
669
670    /// `Unscaled` does the same arithmetic `Scale26Dot6` does at no size,
671    /// without the round trip through 26.6, so the two agree exactly.
672    #[test]
673    fn unscaled_matches_26dot6_without_a_size() {
674        for gid in [0, 1, 5, 7] {
675            let fixed = build(
676                font_test_data::GLYF_COMPONENTS,
677                gid,
678                &Scale26Dot6::new(None, 1000),
679            );
680            let raw = build(font_test_data::GLYF_COMPONENTS, gid, &Unscaled);
681            assert_eq!(raw.contours, fixed.contours, "gid {gid}");
682            assert_eq!(raw.points.len(), fixed.points.len(), "gid {gid}");
683            for (r, f) in raw.points.iter().zip(&fixed.points) {
684                assert_eq!(r.x, f.x.to_bits() >> 6, "gid {gid}");
685                assert_eq!(r.y, f.y.to_bits() >> 6, "gid {gid}");
686            }
687        }
688    }
689
690    /// A composite contributes its components' points and contours.
691    #[test]
692    fn composite_inherits_its_components() {
693        let scale = Scale26Dot6::new(None, 1000);
694        let simple = build(font_test_data::GLYF_COMPONENTS, 1, &scale).points;
695        // Glyph 5 references glyph 1 twice, at different offsets.
696        let built = build(font_test_data::GLYF_COMPONENTS, 5, &scale);
697        let composite = built.points;
698        assert_eq!(composite.len(), simple.len() * 2);
699        assert_eq!(built.contours.len(), 2);
700        // The two copies are placed differently.
701        assert_ne!(composite[0], composite[simple.len()]);
702    }
703
704    /// An empty glyph produces no points but still carries metrics.
705    #[test]
706    fn empty_glyph_has_metrics_but_no_points() {
707        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
708        let context = OutlineTables::new(&font).unwrap();
709        let mut outline = Outline::<Scale26Dot6>::new();
710        let out = outline.load(&context, GlyphId::new(0), Some(16.0)).unwrap();
711        assert!(out.points.is_empty());
712        assert!(out.contours.is_empty());
713        assert!(out.adjusted_advance_width() != F26Dot6::ZERO);
714    }
715
716    /// Buffers smaller than `buffer_lengths` asks for are reported rather than
717    /// overrun.
718    #[test]
719    fn short_buffers_are_rejected() {
720        let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
721        let context = OutlineTables::new(&font).unwrap();
722        let plan = OutlinePlan::new(&context, GlyphId::new(1)).unwrap();
723        let scale = Scale26Dot6::new(None, 1000);
724        let lengths = plan.buffer_lengths::<Scale26Dot6>();
725        // Deliberately one point long, which is why this cannot use `Outline`:
726        // that sizes its buffers to fit.
727        let mut points = vec![Point::default(); 1];
728        let mut flags = vec![PointFlags::default(); lengths.points as usize];
729        let mut contours = vec![0u16; lengths.contours as usize];
730        let mut unscaled = vec![Point::default(); lengths.unscaled as usize];
731        let buffers = Buffers {
732            points: &mut points,
733            flags: &mut flags,
734            contours: &mut contours,
735            unscaled: &mut unscaled,
736            deltas: &mut [],
737            iup: &mut [],
738            composite_deltas: &mut [],
739        };
740        assert_eq!(
741            plan.load(&context, &scale, buffers, None).err(),
742            Some(Error::InsufficientMemory)
743        );
744    }
745}