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}