Coverage Report

Created: 2026-04-01 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fontations/skrifa/src/outline/glyf/hint/definition.rs
Line
Count
Source
1
//! Management of function and instruction definitions.
2
3
use core::ops::Range;
4
5
use super::{error::HintErrorKind, program::Program};
6
7
/// Code range and properties for a function or instruction definition.
8
// Note: this type is designed to support allocation from user memory
9
// so make sure the fields are all tightly packed and only use integral
10
// types.
11
// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttobjs.h#L158>
12
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
13
#[repr(C)]
14
pub struct Definition {
15
    start: u32,
16
    end: u32,
17
    /// The function number for an FDEF or opcode for an IDEF.
18
    key: i32,
19
    _pad: u16,
20
    program: u8,
21
    is_active: u8,
22
}
23
24
impl Definition {
25
    /// Creates a new definition with the given program, code range and
26
    /// key.
27
    ///
28
    /// The key is either a function number or opcode for function and
29
    /// instruction definitions respectively.
30
0
    pub fn new(program: Program, code_range: Range<usize>, key: i32) -> Self {
31
0
        Self {
32
0
            program: program as u8,
33
0
            // Table sizes are specified in u32 so valid ranges will
34
0
            // always fit.
35
0
            start: code_range.start as u32,
36
0
            end: code_range.end as u32,
37
0
            key,
38
0
            _pad: 0,
39
0
            is_active: 1,
40
0
        }
41
0
    }
42
43
    /// Returns the program that contains this definition.
44
0
    pub fn program(&self) -> Program {
45
0
        match self.program {
46
0
            0 => Program::Font,
47
0
            1 => Program::ControlValue,
48
0
            _ => Program::Glyph,
49
        }
50
0
    }
51
52
    /// Returns the function number or opcode.
53
    #[cfg(test)]
54
    pub fn key(&self) -> i32 {
55
        self.key
56
    }
57
58
    /// Returns the byte range of the code for this definition in the source
59
    /// program.
60
0
    pub fn code_range(&self) -> Range<usize> {
61
0
        self.start as usize..self.end as usize
62
0
    }
63
64
    /// Returns true if this definition entry has been defined by a program.
65
0
    pub fn is_active(&self) -> bool {
66
0
        self.is_active != 0
67
0
    }
68
}
69
70
/// Map of function number or opcode to code definitions.
71
///
72
/// The `Ref` vs `Mut` distinction exists because these can be modified
73
/// from the font and control value programs but not from a glyph program.
74
/// In addition, hinting instance state is immutable once initialized so
75
/// this captures that in a type safe way.
76
pub enum DefinitionMap<'a> {
77
    Ref(&'a [Definition]),
78
    Mut(&'a mut [Definition]),
79
}
80
81
impl DefinitionMap<'_> {
82
    /// Attempts to allocate a new definition entry with the given key.
83
    ///
84
    /// Overriding a definition is legal, so if an existing active entry
85
    /// is found with the same key, that one will be returned. Otherwise,
86
    /// an inactive entry will be chosen.
87
0
    pub fn allocate(&mut self, key: i32) -> Result<&mut Definition, HintErrorKind> {
88
0
        let Self::Mut(defs) = self else {
89
0
            return Err(HintErrorKind::DefinitionInGlyphProgram);
90
        };
91
        // First, see if we can use key as an index.
92
        //
93
        // For function definitions in well-behaved fonts (that is, where
94
        // function numbers fall within 0..max_function_defs) this will
95
        // always work.
96
        //
97
        // For instruction definitions, this will likely never work
98
        // because the number of instruction definitions is usually small
99
        // (nearly always 0) and the available opcodes are in the higher
100
        // ranges of u8 space.
101
0
        let ix = if defs
102
0
            .get(key as usize)
103
0
            .filter(|def| !def.is_active() || def.key == key)
104
0
            .is_some()
105
        {
106
            // If the entry is inactive or the key matches, we're good.
107
0
            key as usize
108
        } else {
109
            // Otherwise, walk backward looking for an active entry with
110
            // a matching key. Keep track of the inactive entry with the
111
            // highest index.
112
0
            let mut last_inactive_ix = None;
113
0
            for (i, def) in defs.iter().enumerate().rev() {
114
0
                if def.is_active() {
115
0
                    if def.key == key {
116
0
                        last_inactive_ix = Some(i);
117
0
                        break;
118
0
                    }
119
0
                } else if last_inactive_ix.is_none() {
120
0
                    last_inactive_ix = Some(i);
121
0
                }
122
            }
123
0
            last_inactive_ix.ok_or(HintErrorKind::TooManyDefinitions)?
124
        };
125
0
        let def = defs.get_mut(ix).ok_or(HintErrorKind::TooManyDefinitions)?;
126
0
        *def = Definition::new(Program::Font, 0..0, key);
127
0
        Ok(def)
128
0
    }
129
130
    /// Returns the definition with the given key.
131
0
    pub fn get(&self, key: i32) -> Result<&Definition, HintErrorKind> {
132
0
        let defs = match self {
133
0
            Self::Mut(defs) => *defs,
134
0
            Self::Ref(defs) => *defs,
135
        };
136
        // Fast path, try to use key as index.
137
0
        if let Some(def) = defs.get(key as usize) {
138
0
            if def.is_active() && def.key == key {
139
0
                return Ok(def);
140
0
            }
141
0
        }
142
        // Otherwise, walk backward doing a linear search.
143
0
        for def in defs.iter().rev() {
144
0
            if def.is_active() && def.key == key {
145
0
                return Ok(def);
146
0
            }
147
        }
148
0
        Err(HintErrorKind::InvalidDefinition(key as _))
149
0
    }
150
151
    /// Returns a reference to the underlying definition slice.
152
    #[cfg(test)]
153
    fn as_slice(&self) -> &[Definition] {
154
        match self {
155
            Self::Ref(defs) => defs,
156
            Self::Mut(defs) => defs,
157
        }
158
    }
159
160
    /// If the map is mutable, resets all definitions to the default
161
    /// value.
162
0
    pub fn reset(&mut self) {
163
0
        if let Self::Mut(defs) = self {
164
0
            defs.fill(Default::default())
165
0
        }
166
0
    }
167
}
168
169
/// State containing font defined functions and instructions.
170
pub struct DefinitionState<'a> {
171
    pub functions: DefinitionMap<'a>,
172
    pub instructions: DefinitionMap<'a>,
173
}
174
175
impl<'a> DefinitionState<'a> {
176
0
    pub fn new(functions: DefinitionMap<'a>, instructions: DefinitionMap<'a>) -> Self {
177
0
        Self {
178
0
            functions,
179
0
            instructions,
180
0
        }
181
0
    }
182
}
183
184
#[cfg(test)]
185
mod tests {
186
    use super::*;
187
188
    #[test]
189
    fn too_many_and_invalid() {
190
        let mut buf = vec![Default::default(); 32];
191
        let mut map = DefinitionMap::Mut(&mut buf);
192
        for i in 0..32 {
193
            map.allocate(i).unwrap();
194
        }
195
        assert!(matches!(
196
            map.allocate(33),
197
            Err(HintErrorKind::TooManyDefinitions)
198
        ));
199
        assert!(matches!(
200
            map.get(33),
201
            Err(HintErrorKind::InvalidDefinition(33))
202
        ));
203
    }
204
205
    /// Test dense allocation where all keys map directly to indices. This is
206
    /// the case for function definitions in well behaved fonts.
207
    #[test]
208
    fn allocate_dense() {
209
        let mut buf = vec![Default::default(); 32];
210
        let mut map = DefinitionMap::Mut(&mut buf);
211
        for i in 0..32 {
212
            map.allocate(i).unwrap();
213
        }
214
        for (i, def) in map.as_slice().iter().enumerate() {
215
            let key = i as i32;
216
            map.get(key).unwrap();
217
            assert_eq!(def.key, key);
218
        }
219
    }
220
221
    /// Test sparse allocation where keys never map to indices. This is
222
    /// generally the case for instruction definitions and would apply
223
    /// to fonts with function definition numbers that all fall outside
224
    /// the range 0..max_function_defs.
225
    #[test]
226
    fn allocate_sparse() {
227
        let mut buf = vec![Default::default(); 3];
228
        let mut map = DefinitionMap::Mut(&mut buf);
229
        let keys = [42, 88, 107];
230
        for key in keys {
231
            map.allocate(key).unwrap();
232
        }
233
        for key in keys {
234
            assert_eq!(map.get(key).unwrap().key, key);
235
        }
236
    }
237
238
    /// Test mixed allocation where some keys map to indices and others are
239
    /// subject to fallback allocation. This would be the case for fonts
240
    /// with function definition numbers where some fall inside the range
241
    /// 0..max_function_defs but others don't.
242
    #[test]
243
    fn allocate_mixed() {
244
        let mut buf = vec![Default::default(); 10];
245
        let mut map = DefinitionMap::Mut(&mut buf);
246
        let keys = [
247
            0, 1, 2, 3, // Directly mapped to indices
248
            123456, -42, -5555, // Fallback allocated
249
            5,     // Also directly mapped
250
            7,     // Would be direct but blocked by prior fallback
251
        ];
252
        for key in keys {
253
            map.allocate(key).unwrap();
254
        }
255
        // Check backing store directly to ensure the expected allocation
256
        // pattern.
257
        let expected = [0, 1, 2, 3, 0, 5, 7, -5555, -42, 123456];
258
        let mapped_keys: Vec<_> = map.as_slice().iter().map(|def| def.key).collect();
259
        assert_eq!(&expected, mapped_keys.as_slice());
260
        // Check that all keys are mapped
261
        for key in keys {
262
            assert_eq!(map.get(key).unwrap().key, key);
263
        }
264
    }
265
}