skrifa/outline/glyf/
memory.rs

1//! Memory allocation for TrueType scaling.
2
3use std::mem::{align_of, size_of};
4
5use read_fonts::{
6    tables::glyf::PointFlags,
7    types::{F26Dot6, Fixed, Point},
8};
9
10use super::{super::Hinting, Outline};
11
12/// Buffers used during HarfBuzz-style glyph scaling.
13pub(crate) struct HarfBuzzOutlineMemory<'a> {
14    pub points: &'a mut [Point<f32>],
15    pub contours: &'a mut [u16],
16    pub flags: &'a mut [PointFlags],
17    pub deltas: &'a mut [Point<f32>],
18    pub iup_buffer: &'a mut [Point<f32>],
19    pub composite_deltas: &'a mut [Point<f32>],
20}
21
22impl<'a> HarfBuzzOutlineMemory<'a> {
23    pub(super) fn new(outline: &Outline, buf: &'a mut [u8]) -> Option<Self> {
24        let (points, buf) = alloc_slice(buf, outline.points)?;
25        let (contours, buf) = alloc_slice(buf, outline.contours)?;
26        let (flags, buf) = alloc_slice(buf, outline.points)?;
27        // Don't allocate any delta buffers if we don't have variations
28        let (deltas, iup_buffer, composite_deltas, _buf) = if outline.has_variations {
29            let (deltas, buf) = alloc_slice(buf, outline.max_simple_points)?;
30            let (iup_buffer, buf) = alloc_slice(buf, outline.max_simple_points)?;
31            let (composite_deltas, buf) = alloc_slice(buf, outline.max_component_delta_stack)?;
32            (deltas, iup_buffer, composite_deltas, buf)
33        } else {
34            (
35                Default::default(),
36                Default::default(),
37                Default::default(),
38                buf,
39            )
40        };
41        Some(Self {
42            points,
43            contours,
44            flags,
45            deltas,
46            iup_buffer,
47            composite_deltas,
48        })
49    }
50}
51
52/// Buffers used during glyph scaling.
53pub(crate) struct FreeTypeOutlineMemory<'a> {
54    pub unscaled: &'a mut [Point<i32>],
55    pub scaled: &'a mut [Point<F26Dot6>],
56    pub original_scaled: &'a mut [Point<F26Dot6>],
57    pub contours: &'a mut [u16],
58    pub flags: &'a mut [PointFlags],
59    pub deltas: &'a mut [Point<Fixed>],
60    pub iup_buffer: &'a mut [Point<Fixed>],
61    pub composite_deltas: &'a mut [Point<Fixed>],
62    pub stack: &'a mut [i32],
63    pub cvt: &'a mut [i32],
64    pub storage: &'a mut [i32],
65    pub twilight_scaled: &'a mut [Point<F26Dot6>],
66    pub twilight_original_scaled: &'a mut [Point<F26Dot6>],
67    pub twilight_flags: &'a mut [PointFlags],
68}
69
70impl<'a> FreeTypeOutlineMemory<'a> {
71    pub(super) fn new(outline: &Outline, buf: &'a mut [u8], hinting: Hinting) -> Option<Self> {
72        let hinted = outline.has_hinting && hinting == Hinting::Embedded;
73        let (scaled, buf) = alloc_slice(buf, outline.points)?;
74        let (unscaled, buf) = alloc_slice(buf, outline.max_other_points)?;
75        // We only need original scaled points when hinting
76        let (original_scaled, buf) = if hinted {
77            alloc_slice(buf, outline.max_other_points)?
78        } else {
79            (Default::default(), buf)
80        };
81        // Don't allocate any delta buffers if we don't have variations
82        let (deltas, iup_buffer, composite_deltas, buf) = if outline.has_variations {
83            let (deltas, buf) = alloc_slice(buf, outline.max_simple_points)?;
84            let (iup_buffer, buf) = alloc_slice(buf, outline.max_simple_points)?;
85            let (composite_deltas, buf) = alloc_slice(buf, outline.max_component_delta_stack)?;
86            (deltas, iup_buffer, composite_deltas, buf)
87        } else {
88            (
89                Default::default(),
90                Default::default(),
91                Default::default(),
92                buf,
93            )
94        };
95        // Hinting value stack
96        let (stack, buf) = if hinted {
97            alloc_slice(buf, outline.max_stack)?
98        } else {
99            (Default::default(), buf)
100        };
101        // Copy-on-write buffers for CVT and storage area
102        let (cvt, storage, buf) = if hinted {
103            let (cvt, buf) = alloc_slice(buf, outline.cvt_count)?;
104            let (storage, buf) = alloc_slice(buf, outline.storage_count)?;
105            (cvt, storage, buf)
106        } else {
107            (Default::default(), Default::default(), buf)
108        };
109        // Twilight zone point buffers
110        let (twilight_scaled, twilight_original_scaled, buf) = if hinted {
111            let (scaled, buf) = alloc_slice(buf, outline.max_twilight_points)?;
112            let (original_scaled, buf) = alloc_slice(buf, outline.max_twilight_points)?;
113            (scaled, original_scaled, buf)
114        } else {
115            (Default::default(), Default::default(), buf)
116        };
117        let (contours, buf) = alloc_slice(buf, outline.contours)?;
118        let (flags, buf) = alloc_slice(buf, outline.points)?;
119        // Twilight zone point flags
120        let twilight_flags = if hinted {
121            alloc_slice(buf, outline.max_twilight_points)?.0
122        } else {
123            Default::default()
124        };
125        Some(Self {
126            unscaled,
127            scaled,
128            original_scaled,
129            contours,
130            flags,
131            deltas,
132            iup_buffer,
133            composite_deltas,
134            stack,
135            cvt,
136            storage,
137            twilight_scaled,
138            twilight_original_scaled,
139            twilight_flags,
140        })
141    }
142}
143
144/// Allocates a mutable slice of `T` of the given length from the specified
145/// buffer.
146///
147/// Returns the allocated slice and the remainder of the buffer.
148fn alloc_slice<T>(buf: &mut [u8], len: usize) -> Option<(&mut [T], &mut [u8])>
149where
150    T: bytemuck::AnyBitPattern + bytemuck::NoUninit,
151{
152    if len == 0 {
153        return Some((Default::default(), buf));
154    }
155    // 1) Ensure we slice the buffer at a position that is properly aligned
156    // for T.
157    let base_ptr = buf.as_ptr() as usize;
158    let aligned_ptr = align_up(base_ptr, align_of::<T>());
159    let aligned_offset = aligned_ptr - base_ptr;
160    let buf = buf.get_mut(aligned_offset..)?;
161    // 2) Ensure we have enough space in the buffer to allocate our slice.
162    let len_in_bytes = len * size_of::<T>();
163    if len_in_bytes > buf.len() {
164        return None;
165    }
166    let (slice_buf, rest) = buf.split_at_mut(len_in_bytes);
167    // Bytemuck handles all safety guarantees here.
168    let slice = bytemuck::try_cast_slice_mut(slice_buf).ok()?;
169    Some((slice, rest))
170}
171
172fn align_up(len: usize, alignment: usize) -> usize {
173    len + (len.wrapping_neg() & (alignment - 1))
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn unaligned_buffer() {
182        let mut buf = [0u8; 40];
183        let alignment = align_of::<i32>();
184        let addr = buf.as_ptr() as usize;
185        let mut unaligned_addr = addr;
186        // Force an unaligned offset
187        if unaligned_addr % alignment == 0 {
188            unaligned_addr += 1;
189        }
190        let unaligned_offset = unaligned_addr - addr;
191        let unaligned = &mut buf[unaligned_offset..];
192        assert!(unaligned.as_ptr() as usize % alignment != 0);
193        let (slice, _) = alloc_slice::<i32>(unaligned, 8).unwrap();
194        assert_eq!(slice.as_ptr() as usize % alignment, 0);
195    }
196
197    #[test]
198    fn fail_unaligned_buffer() {
199        let mut buf = [0u8; 40];
200        let alignment = align_of::<i32>();
201        let addr = buf.as_ptr() as usize;
202        let mut unaligned_addr = addr;
203        // Force an unaligned offset
204        if unaligned_addr % alignment == 0 {
205            unaligned_addr += 1;
206        }
207        let unaligned_offset = unaligned_addr - addr;
208        let unaligned = &mut buf[unaligned_offset..];
209        assert_eq!(alloc_slice::<i32>(unaligned, 16), None);
210    }
211
212    #[test]
213    fn outline_memory() {
214        let outline_info = Outline {
215            glyph: None,
216            glyph_id: Default::default(),
217            points: 10,
218            contours: 4,
219            max_simple_points: 4,
220            max_other_points: 4,
221            max_component_delta_stack: 4,
222            max_stack: 0,
223            cvt_count: 0,
224            storage_count: 0,
225            max_twilight_points: 0,
226            has_hinting: false,
227            has_variations: true,
228            has_overlaps: false,
229        };
230        let required_size = outline_info.required_buffer_size(Hinting::None);
231        let mut buf = vec![0u8; required_size];
232        let memory = FreeTypeOutlineMemory::new(&outline_info, &mut buf, Hinting::None).unwrap();
233        assert_eq!(memory.scaled.len(), outline_info.points);
234        assert_eq!(memory.unscaled.len(), outline_info.max_other_points);
235        // We don't allocate this buffer when hinting is disabled
236        assert_eq!(memory.original_scaled.len(), 0);
237        assert_eq!(memory.flags.len(), outline_info.points);
238        assert_eq!(memory.contours.len(), outline_info.contours);
239        assert_eq!(memory.deltas.len(), outline_info.max_simple_points);
240        assert_eq!(memory.iup_buffer.len(), outline_info.max_simple_points);
241        assert_eq!(
242            memory.composite_deltas.len(),
243            outline_info.max_component_delta_stack
244        );
245    }
246
247    #[test]
248    fn fail_outline_memory() {
249        let outline_info = Outline {
250            glyph: None,
251            glyph_id: Default::default(),
252            points: 10,
253            contours: 4,
254            max_simple_points: 4,
255            max_other_points: 4,
256            max_component_delta_stack: 4,
257            max_stack: 0,
258            cvt_count: 0,
259            storage_count: 0,
260            max_twilight_points: 0,
261            has_hinting: false,
262            has_variations: true,
263            has_overlaps: false,
264        };
265        // Required size adds 4 bytes slop to account for internal alignment
266        // requirements. So subtract 5 to force a failure.
267        let not_enough = outline_info.required_buffer_size(Hinting::None) - 5;
268        let mut buf = vec![0u8; not_enough];
269        assert!(FreeTypeOutlineMemory::new(&outline_info, &mut buf, Hinting::None).is_none());
270    }
271}