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);
    }
}