skrifa/outline/glyf/hint/
call_stack.rs

1//! Tracking function call state.
2
3use super::{definition::Definition, error::HintErrorKind, program::Program};
4
5// FreeType provides a call stack with a depth of 32.
6// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L502>
7const MAX_DEPTH: usize = 32;
8
9/// Record of an active invocation of a function or instruction
10/// definition.
11///
12/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.h#L90>
13#[derive(Copy, Clone, Default)]
14pub struct CallRecord {
15    pub caller_program: Program,
16    pub return_pc: usize,
17    pub current_count: u32,
18    pub definition: Definition,
19}
20
21/// Tracker for nested active function or instruction calls.
22#[derive(Default)]
23pub struct CallStack {
24    records: [CallRecord; MAX_DEPTH],
25    len: usize,
26}
27
28impl CallStack {
29    pub fn clear(&mut self) {
30        self.len = 0;
31    }
32
33    pub fn push(&mut self, record: CallRecord) -> Result<(), HintErrorKind> {
34        let top = self
35            .records
36            .get_mut(self.len)
37            .ok_or(HintErrorKind::CallStackOverflow)?;
38        *top = record;
39        self.len += 1;
40        Ok(())
41    }
42
43    pub fn peek(&self) -> Option<&CallRecord> {
44        self.records.get(self.len.checked_sub(1)?)
45    }
46
47    pub fn pop(&mut self) -> Result<CallRecord, HintErrorKind> {
48        let record = *self.peek().ok_or(HintErrorKind::CallStackUnderflow)?;
49        self.len -= 1;
50        Ok(record)
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn stack_overflow() {
60        let mut stack = CallStack::default();
61        for i in 0..MAX_DEPTH {
62            stack.push(record_with_key(i)).unwrap();
63        }
64        assert!(matches!(
65            stack.push(CallRecord::default()),
66            Err(HintErrorKind::CallStackOverflow)
67        ));
68    }
69
70    #[test]
71    fn stack_underflow() {
72        assert!(matches!(
73            CallStack::default().pop(),
74            Err(HintErrorKind::CallStackUnderflow)
75        ));
76    }
77
78    #[test]
79    fn stack_push_pop() {
80        let mut stack = CallStack::default();
81        for i in 0..MAX_DEPTH {
82            stack.push(record_with_key(i)).unwrap();
83        }
84        for i in (0..MAX_DEPTH).rev() {
85            assert_eq!(stack.pop().unwrap().definition.key(), i as i32);
86        }
87    }
88
89    fn record_with_key(key: usize) -> CallRecord {
90        CallRecord {
91            caller_program: Program::Glyph,
92            return_pc: 0,
93            current_count: 1,
94            definition: Definition::new(Program::Font, 0..0, key as i32),
95        }
96    }
97}