skrifa/outline/glyf/hint/
cow_slice.rs

1//! Copy-on-write buffer for CVT and storage area.
2
3/// Backing store for the CVT and storage area.
4///
5/// The CVT and storage area are initialized in the control value program
6/// with values that are relevant to a particular size and hinting
7/// configuration. However, some fonts contain code in glyph programs
8/// that write to these buffers. Any modifications made in a glyph program
9/// should not affect future glyphs and thus should not persist beyond
10/// execution of that program. To solve this problem, a copy of the buffer
11/// is made on the first write in a glyph program and all changes are
12/// discarded on completion.
13///
14/// For more context, see <https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/23>
15///
16/// # Implementation notes
17///
18/// The current implementation defers the copy but not the allocation. This
19/// is to support the guarantee of no heap allocation when operating on user
20/// provided memory. Investigation of hinted Noto fonts suggests that writing
21/// to CVT/Storage in glyph programs is common for ttfautohinted fonts so the
22/// speculative allocation is likely worthwhile.
23pub struct CowSlice<'a> {
24    data: &'a [i32],
25    data_mut: &'a mut [i32],
26    /// True if we've initialized the mutable slice
27    use_mut: bool,
28}
29
30impl<'a> CowSlice<'a> {
31    /// Creates a new copy-on-write slice with the given buffers.
32    ///
33    /// The `data` buffer is expected to contain the initial data and the content
34    /// of `data_mut` is ignored unless the [`set`](Self::set) method is called
35    /// in which case a copy will be made from `data` to `data_mut` and the
36    /// mutable buffer will be used for all further access.
37    ///
38    /// Returns [`CowSliceSizeMismatchError`] if `data.len() != data_mut.len()`.
39    pub fn new(
40        data: &'a [i32],
41        data_mut: &'a mut [i32],
42    ) -> Result<Self, CowSliceSizeMismatchError> {
43        if data.len() != data_mut.len() {
44            return Err(CowSliceSizeMismatchError(data.len(), data_mut.len()));
45        }
46        Ok(Self {
47            data,
48            data_mut,
49            use_mut: false,
50        })
51    }
52
53    /// Creates a new copy-on-write slice with the given mutable buffer.
54    ///
55    /// This avoids an extra copy and allocation in contexts where the data is
56    /// already assumed to be mutable (i.e. when executing `fpgm` and `prep`
57    /// programs).
58    pub fn new_mut(data_mut: &'a mut [i32]) -> Self {
59        Self {
60            use_mut: true,
61            data: &[],
62            data_mut,
63        }
64    }
65
66    /// Returns the value at the given index.
67    ///
68    /// If mutable data has been initialized, reads from that buffer. Otherwise
69    /// reads from the immutable buffer.
70    pub fn get(&self, index: usize) -> Option<i32> {
71        if self.use_mut {
72            self.data_mut.get(index).copied()
73        } else {
74            self.data.get(index).copied()
75        }
76    }
77
78    /// Writes a value to the given index.
79    ///
80    /// If the mutable buffer hasn't been initialized, first performs a full
81    /// buffer copy.
82    pub fn set(&mut self, index: usize, value: i32) -> Option<()> {
83        // Copy from immutable to mutable buffer if we haven't already
84        if !self.use_mut {
85            self.data_mut.copy_from_slice(self.data);
86            self.use_mut = true;
87        }
88        *self.data_mut.get_mut(index)? = value;
89        Some(())
90    }
91
92    pub fn len(&self) -> usize {
93        if self.use_mut {
94            self.data_mut.len()
95        } else {
96            self.data.len()
97        }
98    }
99}
100
101/// Error returned when the sizes of the immutable and mutable buffers
102/// mismatch when constructing a [`CowSlice`].
103#[derive(Clone, Debug)]
104pub struct CowSliceSizeMismatchError(usize, usize);
105
106impl std::fmt::Display for CowSliceSizeMismatchError {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(
109            f,
110            "size mismatch for immutable and mutable buffers: data.len() = {}, data_mut.len() = {}",
111            self.0, self.1
112        )
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::{CowSlice, CowSliceSizeMismatchError};
119
120    #[test]
121    fn size_mismatch_error() {
122        let data_mut = &mut [0, 0];
123        let result = CowSlice::new(&[1, 2, 3], data_mut);
124        assert!(matches!(result, Err(CowSliceSizeMismatchError(3, 2))))
125    }
126
127    #[test]
128    fn copy_on_write() {
129        let data = std::array::from_fn::<_, 16, _>(|i| i as i32);
130        let mut data_mut = [0i32; 16];
131        let mut slice = CowSlice::new(&data, &mut data_mut).unwrap();
132        // Not mutable yet
133        assert!(!slice.use_mut);
134        for i in 0..data.len() {
135            assert_eq!(slice.get(i).unwrap(), i as i32);
136        }
137        // Modify all values
138        for i in 0..data.len() {
139            let value = slice.get(i).unwrap();
140            slice.set(i, value * 2).unwrap();
141        }
142        // Now we're mutable
143        assert!(slice.use_mut);
144        for i in 0..data.len() {
145            assert_eq!(slice.get(i).unwrap(), i as i32 * 2);
146        }
147    }
148
149    #[test]
150    fn out_of_bounds() {
151        let data_mut = &mut [1, 2];
152        let slice = CowSlice::new_mut(data_mut);
153        assert_eq!(slice.get(0), Some(1));
154        assert_eq!(slice.get(1), Some(2));
155        assert_eq!(slice.get(2), None);
156    }
157}