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