Skip to main content

read_fonts/tables/glyf/outline/
buffers.rs

1//! Scratch space for loading an outline, and how much to supply.
2
3use super::{
4    super::{Point, PointFlags},
5    scale::Scale,
6};
7use crate::mem::take_slice;
8use crate::model::Error;
9use bytemuck::{AnyBitPattern, NoUninit};
10
11/// Scratch space for [`OutlinePlan::load`](super::OutlinePlan::load).
12///
13/// Every buffer comes from the caller. Size them with
14/// [`OutlinePlan::buffer_lengths`](super::OutlinePlan::buffer_lengths); a buffer
15/// shorter than that is reported as [`Error::InsufficientMemory`], not
16/// overrun.
17///
18/// [`Error::InsufficientMemory`]: crate::model::Error::InsufficientMemory
19pub struct Buffers<'a, S: Scale> {
20    /// Receives the loaded points.
21    pub points: &'a mut [Point<S::Coord>],
22    /// Receives the point flags.
23    pub flags: &'a mut [PointFlags],
24    /// Receives the contour end point indices.
25    pub contours: &'a mut [u16],
26    /// Font unit coordinates. Read only when [`Scale::NEEDS_UNSCALED`],
27    /// and may be empty otherwise.
28    pub unscaled: &'a mut [Point<i32>],
29    /// Variation delta scratch. May be empty for a static instance.
30    pub deltas: &'a mut [Point<S::Delta>],
31    /// Delta interpolation scratch. May be empty for a static instance.
32    pub iup: &'a mut [Point<S::Delta>],
33    /// Per-component delta stack. May be empty for a static instance.
34    pub composite_deltas: &'a mut [Point<S::Delta>],
35}
36
37/// How long each scratch buffer an outline needs has to be.
38///
39/// These are element counts, not byte lengths: the element types differ per
40/// buffer and, for the coordinate buffers, per [`Scale`]. For the bytes one
41/// block needs to hold them all, see [`packed_len`][Self::packed_len].
42///
43/// Created by [`OutlinePlan::buffer_lengths`](super::OutlinePlan::buffer_lengths).
44#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
45pub struct BufferLengths {
46    /// Scaled points, including phantom points. The flag buffer is this long
47    /// too, since there is one flag per point.
48    pub points: u32,
49    /// Contour end point indices.
50    pub contours: u32,
51    /// Points in font units, or zero when the [`Scale`] does not keep them.
52    pub unscaled: u32,
53    /// Accumulated variation deltas, or zero for a non-variable draw.
54    pub deltas: u32,
55    /// Working space for interpolating sparse deltas.
56    pub iup: u32,
57    /// Per-component deltas for composite glyphs.
58    pub composite_deltas: u32,
59}
60
61impl BufferLengths {
62    /// The number of bytes [`Buffers::from_bytes`] needs to carve every
63    /// buffer from one block.
64    ///
65    /// Includes room to align the block, which need not be aligned already.
66    /// Zero when the outline needs no storage.
67    pub fn packed_len<S: Scale>(&self) -> usize {
68        let coord = size_of::<Point<S::Coord>>();
69        let delta = size_of::<Point<S::Delta>>();
70        let size = self.points as usize * (coord + size_of::<PointFlags>())
71            + self.contours as usize * size_of::<u16>()
72            + self.unscaled as usize * size_of::<Point<i32>>()
73            + (self.deltas + self.iup + self.composite_deltas) as usize * delta;
74        if size == 0 {
75            return 0;
76        }
77        // Buffers are carved out in descending order of alignment, so only the
78        // first one can need padding, and one alignment's worth covers it.
79        size + align_of::<Point<S::Coord>>()
80            .max(align_of::<Point<S::Delta>>())
81            .max(align_of::<Point<i32>>())
82    }
83}
84
85impl<'a, S: Scale> Buffers<'a, S> {
86    /// Carves all seven buffers out of one block of bytes.
87    ///
88    /// For a caller with its own allocator, or one that prefers a single
89    /// allocation per draw. `bytes` must be at least
90    /// [`BufferLengths::packed_len`] long and need not be aligned.
91    ///
92    /// ```
93    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
94    /// use read_fonts::{
95    ///     tables::glyf::outline::{
96    ///         Buffers, OutlinePlan, OutlineTables, Scale26Dot6,
97    ///     },
98    ///     types::GlyphId,
99    ///     FontRef,
100    /// };
101    ///
102    /// let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE)?;
103    /// let tables = OutlineTables::new(&font)?;
104    /// let plan = OutlinePlan::new(&tables, GlyphId::new(1))?;
105    ///
106    /// let lengths = plan.buffer_lengths::<Scale26Dot6>();
107    /// let mut block = vec![0u8; lengths.packed_len::<Scale26Dot6>()];
108    /// let buffers = Buffers::from_bytes(&mut block, &lengths)?;
109    ///
110    /// let scale = Scale26Dot6::new(Some(16.0), tables.units_per_em);
111    /// let outline = plan.load(&tables, &scale, buffers, None)?;
112    /// assert_eq!(outline.points().len(), outline.flags().len());
113    /// # Ok(())
114    /// # }
115    /// ```
116    pub fn from_bytes(bytes: &'a mut [u8], lengths: &BufferLengths) -> Result<Self, Error> {
117        // Descending alignment, so only the first allocation can need padding.
118        let (points, bytes) = take(bytes, lengths.points)?;
119        let (unscaled, bytes) = take(bytes, lengths.unscaled)?;
120        let (deltas, bytes) = take(bytes, lengths.deltas)?;
121        let (iup, bytes) = take(bytes, lengths.iup)?;
122        let (composite_deltas, bytes) = take(bytes, lengths.composite_deltas)?;
123        let (contours, bytes) = take(bytes, lengths.contours)?;
124        let (flags, _) = take(bytes, lengths.points)?;
125        Ok(Self {
126            points,
127            flags,
128            contours,
129            unscaled,
130            deltas,
131            iup,
132            composite_deltas,
133        })
134    }
135}
136
137/// Takes a `len` element slice of `T` off the front of `bytes`, in the terms
138/// an outline reports.
139fn take<T>(bytes: &mut [u8], len: u32) -> Result<(&mut [T], &mut [u8]), Error>
140where
141    T: AnyBitPattern + NoUninit,
142{
143    take_slice(bytes, len as usize).ok_or(Error::InsufficientMemory)
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::{
150        tables::glyf::outline::{
151            Outline, OutlinePlan, OutlineTables, Scale26Dot6, ScaleF32, Unscaled,
152        },
153        types::{F26Dot6, F2Dot14, GlyphId},
154        FontRef, TableProvider,
155    };
156    use alloc::vec;
157
158    /// An outline built in one block of bytes matches one built in separate
159    /// vectors, for every glyph of a font and at every scale.
160    #[test]
161    fn packed_buffers_load_the_same_outline() {
162        fn check<S: Scale>(scale: &S)
163        where
164            S::Coord: core::fmt::Debug,
165        {
166            let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
167            let tables = OutlineTables::new(&font).unwrap();
168            for gid in 0..font.maxp().unwrap().num_glyphs() {
169                let gid = GlyphId::from(gid);
170                let plan = OutlinePlan::new(&tables, gid).unwrap();
171                let lengths = plan.buffer_lengths::<S>();
172
173                let mut owned = Outline::<S>::new();
174                let expected = owned.load_with(&tables, gid, scale, None).unwrap();
175                let expected = (expected.points().to_vec(), expected.contours().to_vec());
176
177                let mut block = vec![0u8; lengths.packed_len::<S>()];
178                let buffers = Buffers::from_bytes(&mut block, &lengths).unwrap();
179                let actual = plan.load(&tables, scale, buffers, None).unwrap();
180                assert_eq!(actual.points(), expected.0, "gid {gid}");
181                assert_eq!(actual.contours(), expected.1, "gid {gid}");
182            }
183        }
184        check(&Scale26Dot6::new(Some(16.0), 1000));
185        check(&ScaleF32::new(Some(16.0), 1000));
186        check(&Unscaled);
187    }
188
189    /// The reported size is enough, and one byte less is not.
190    #[test]
191    fn the_reported_size_is_what_it_takes() {
192        let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
193        let tables = OutlineTables::new(&font).unwrap();
194        let plan = OutlinePlan::new(&tables, GlyphId::new(5)).unwrap();
195        let lengths = plan.buffer_lengths::<Scale26Dot6>();
196        let size = lengths.packed_len::<Scale26Dot6>();
197        assert!(size > 0);
198
199        let mut block = vec![0u8; size];
200        assert!(Buffers::<Scale26Dot6>::from_bytes(&mut block, &lengths).is_ok());
201
202        // The slack is for alignment, so a short block only has to fail when
203        // it is short by more than that slack.
204        let mut short = vec![0u8; size - core::mem::align_of::<Point<F26Dot6>>() - 1];
205        assert_eq!(
206            Buffers::<Scale26Dot6>::from_bytes(&mut short, &lengths).err(),
207            Some(Error::InsufficientMemory)
208        );
209    }
210
211    /// A block that does not begin on an alignment boundary still works.
212    #[test]
213    fn an_unaligned_block_is_fine() {
214        let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
215        let tables = OutlineTables::new(&font).unwrap();
216        let plan = OutlinePlan::new(&tables, GlyphId::new(5)).unwrap();
217        let lengths = plan.buffer_lengths::<Scale26Dot6>();
218        let scale = Scale26Dot6::new(Some(16.0), 1000);
219
220        let aligned = {
221            let mut block = vec![0u8; lengths.packed_len::<Scale26Dot6>()];
222            let buffers = Buffers::from_bytes(&mut block, &lengths).unwrap();
223            plan.load(&tables, &scale, buffers, None)
224                .unwrap()
225                .points()
226                .to_vec()
227        };
228        for offset in 1..8 {
229            let mut block = vec![0u8; lengths.packed_len::<Scale26Dot6>() + offset];
230            let buffers = Buffers::from_bytes(&mut block[offset..], &lengths).unwrap();
231            let outline = plan.load(&tables, &scale, buffers, None).unwrap();
232            assert_eq!(outline.points(), aligned, "offset {offset}");
233        }
234    }
235
236    /// Every carved buffer is at least as long as its count asks for, and
237    /// correctly aligned.
238    #[test]
239    fn every_buffer_is_sized_and_aligned() {
240        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
241        // At a location, so the delta buffers are non-empty too.
242        let coords = [F2Dot14::from_f32(1.0)];
243        let tables = OutlineTables::new(&font).unwrap().at(&coords, &[]);
244        let plan = OutlinePlan::new(&tables, GlyphId::new(2)).unwrap();
245        assert!(plan.applies_variations);
246        let lengths = plan.buffer_lengths::<Scale26Dot6>();
247        let mut block = vec![0u8; lengths.packed_len::<Scale26Dot6>()];
248        let buffers = Buffers::<Scale26Dot6>::from_bytes(&mut block, &lengths).unwrap();
249        assert_eq!(buffers.points.len(), lengths.points as usize);
250        assert_eq!(buffers.flags.len(), lengths.points as usize);
251        assert_eq!(buffers.contours.len(), lengths.contours as usize);
252        assert_eq!(buffers.unscaled.len(), lengths.unscaled as usize);
253        assert_eq!(buffers.deltas.len(), lengths.deltas as usize);
254        assert_eq!(buffers.iup.len(), lengths.iup as usize);
255        assert_eq!(
256            buffers.composite_deltas.len(),
257            lengths.composite_deltas as usize
258        );
259        assert!(lengths.deltas > 0, "expected a varying glyph");
260        // Casting through bytemuck would have failed on a misaligned slice,
261        // but check the pointers rather than trust that.
262        assert_eq!(buffers.points.as_ptr().align_offset(4), 0);
263        assert_eq!(buffers.contours.as_ptr().align_offset(2), 0);
264    }
265
266    /// An empty glyph needs no storage at all.
267    #[test]
268    fn an_empty_outline_needs_no_bytes() {
269        let lengths = BufferLengths::default();
270        assert_eq!(lengths.packed_len::<Scale26Dot6>(), 0);
271        let buffers = Buffers::<Scale26Dot6>::from_bytes(&mut [], &lengths).unwrap();
272        assert!(buffers.points.is_empty());
273        assert!(buffers.flags.is_empty());
274    }
275}