skrifa/outline/glyf/hint/engine/
mod.rs

1//! TrueType bytecode interpreter.
2
3mod arith;
4mod control_flow;
5mod cvt;
6mod data;
7mod definition;
8mod delta;
9mod dispatch;
10mod graphics;
11mod logical;
12mod misc;
13mod outline;
14mod round;
15mod stack;
16mod storage;
17
18use read_fonts::{
19    tables::glyf::bytecode::Instruction,
20    types::{F26Dot6, F2Dot14, Point},
21};
22
23use super::{
24    super::Outlines,
25    cvt::Cvt,
26    definition::DefinitionState,
27    error::{HintError, HintErrorKind},
28    graphics::{GraphicsState, RetainedGraphicsState},
29    math,
30    program::ProgramState,
31    storage::Storage,
32    value_stack::ValueStack,
33    zone::Zone,
34};
35
36pub type OpResult = Result<(), HintErrorKind>;
37
38/// TrueType bytecode interpreter.
39pub struct Engine<'a> {
40    program: ProgramState<'a>,
41    graphics: GraphicsState<'a>,
42    definitions: DefinitionState<'a>,
43    cvt: Cvt<'a>,
44    storage: Storage<'a>,
45    value_stack: ValueStack<'a>,
46    loop_budget: LoopBudget,
47    axis_count: u16,
48    coords: &'a [F2Dot14],
49}
50
51impl<'a> Engine<'a> {
52    #[allow(clippy::too_many_arguments)]
53    pub fn new(
54        outlines: &Outlines,
55        program: ProgramState<'a>,
56        graphics: RetainedGraphicsState,
57        definitions: DefinitionState<'a>,
58        cvt: impl Into<Cvt<'a>>,
59        storage: impl Into<Storage<'a>>,
60        value_stack: ValueStack<'a>,
61        twilight: Zone<'a>,
62        glyph: Zone<'a>,
63        axis_count: u16,
64        coords: &'a [F2Dot14],
65        is_composite: bool,
66    ) -> Self {
67        let point_count = if glyph.points.is_empty() {
68            None
69        } else {
70            Some(glyph.points.len())
71        };
72        let graphics = GraphicsState {
73            retained: graphics,
74            zones: [twilight, glyph],
75            is_composite,
76            ..Default::default()
77        };
78        Self {
79            program,
80            graphics,
81            definitions,
82            cvt: cvt.into(),
83            storage: storage.into(),
84            value_stack,
85            loop_budget: LoopBudget::new(outlines, point_count),
86            axis_count,
87            coords,
88        }
89    }
90
91    pub fn backward_compatibility(&self) -> bool {
92        self.graphics.backward_compatibility
93    }
94
95    pub fn retained_graphics_state(&self) -> &RetainedGraphicsState {
96        &self.graphics.retained
97    }
98}
99
100/// Tracks budgets for loops to limit execution time.
101struct LoopBudget {
102    /// Maximum number of times we can do backward jumps or
103    /// loop calls.
104    limit: usize,
105    /// Current number of backward jumps executed.
106    backward_jumps: usize,
107    /// Current number of loop call iterations executed.
108    loop_calls: usize,
109}
110
111impl LoopBudget {
112    fn new(outlines: &Outlines, point_count: Option<usize>) -> Self {
113        let cvt_len = outlines.cvt_len as usize;
114        // Compute limits for loop calls and backward jumps.
115        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L6955>
116        let limit = if let Some(point_count) = point_count {
117            (point_count * 10).max(50) + (cvt_len / 10).max(50)
118        } else {
119            300 + 22 * cvt_len
120        };
121        // FreeType has two variables for neg_jump_counter_max and
122        // loopcall_counter_max but sets them to the same value so
123        // we'll just use a single limit.
124        Self {
125            limit,
126            backward_jumps: 0,
127            loop_calls: 0,
128        }
129    }
130
131    fn reset(&mut self) {
132        self.backward_jumps = 0;
133        self.loop_calls = 0;
134    }
135
136    fn doing_backward_jump(&mut self) -> Result<(), HintErrorKind> {
137        self.backward_jumps += 1;
138        if self.backward_jumps > self.limit {
139            Err(HintErrorKind::ExceededExecutionBudget)
140        } else {
141            Ok(())
142        }
143    }
144
145    fn doing_loop_call(&mut self, count: usize) -> Result<(), HintErrorKind> {
146        self.loop_calls += count;
147        if self.loop_calls > self.limit {
148            Err(HintErrorKind::ExceededExecutionBudget)
149        } else {
150            Ok(())
151        }
152    }
153}
154
155#[cfg(test)]
156use mock::MockEngine;
157
158#[cfg(test)]
159mod mock {
160    use super::{
161        super::{
162            cow_slice::CowSlice,
163            definition::{Definition, DefinitionMap, DefinitionState},
164            program::{Program, ProgramState},
165            zone::Zone,
166            Point, PointFlags,
167        },
168        Engine, F26Dot6, GraphicsState, LoopBudget, ValueStack,
169    };
170
171    /// Mock engine for testing.
172    pub(super) struct MockEngine {
173        cvt_storage: Vec<i32>,
174        value_stack: Vec<i32>,
175        definitions: Vec<Definition>,
176        unscaled: Vec<Point<i32>>,
177        points: Vec<Point<F26Dot6>>,
178        point_flags: Vec<PointFlags>,
179        contours: Vec<u16>,
180        twilight: Vec<Point<F26Dot6>>,
181        twilight_flags: Vec<PointFlags>,
182    }
183
184    impl MockEngine {
185        pub fn new() -> Self {
186            Self {
187                cvt_storage: vec![0; 32],
188                value_stack: vec![0; 32],
189                definitions: vec![Default::default(); 8],
190                unscaled: vec![Default::default(); 32],
191                points: vec![Default::default(); 64],
192                point_flags: vec![Default::default(); 32],
193                contours: vec![31],
194                twilight: vec![Default::default(); 32],
195                twilight_flags: vec![Default::default(); 32],
196            }
197        }
198
199        pub fn engine(&mut self) -> Engine {
200            let font_code = &[];
201            let cv_code = &[];
202            let glyph_code = &[];
203            let (cvt, storage) = self.cvt_storage.split_at_mut(16);
204            let (function_defs, instruction_defs) = self.definitions.split_at_mut(5);
205            let definition = DefinitionState::new(
206                DefinitionMap::Mut(function_defs),
207                DefinitionMap::Mut(instruction_defs),
208            );
209            for (i, point) in self.unscaled.iter_mut().enumerate() {
210                let i = i as i32;
211                point.x = 57 + i * 2;
212                point.y = -point.x * 3;
213            }
214            let (points, original) = self.points.split_at_mut(32);
215            let glyph_zone = Zone::new(
216                &self.unscaled,
217                original,
218                points,
219                &mut self.point_flags,
220                &self.contours,
221            );
222            let (points, original) = self.twilight.split_at_mut(16);
223            let twilight_zone = Zone::new(&[], original, points, &mut self.twilight_flags, &[]);
224            let mut graphics_state = GraphicsState {
225                zones: [twilight_zone, glyph_zone],
226                ..Default::default()
227            };
228            graphics_state.update_projection_state();
229            Engine {
230                graphics: graphics_state,
231                cvt: CowSlice::new_mut(cvt).into(),
232                storage: CowSlice::new_mut(storage).into(),
233                value_stack: ValueStack::new(&mut self.value_stack, false),
234                program: ProgramState::new(font_code, cv_code, glyph_code, Program::Font),
235                loop_budget: LoopBudget {
236                    limit: 10,
237                    backward_jumps: 0,
238                    loop_calls: 0,
239                },
240                definitions: definition,
241                axis_count: 0,
242                coords: &[],
243            }
244        }
245    }
246
247    impl Default for MockEngine {
248        fn default() -> Self {
249            Self::new()
250        }
251    }
252
253    impl Engine<'_> {
254        /// Helper to push values to the stack, invoke a callback and check
255        /// the expected result.    
256        pub(super) fn test_exec(
257            &mut self,
258            push: &[i32],
259            expected_result: impl Into<i32>,
260            mut f: impl FnMut(&mut Engine),
261        ) {
262            for &val in push {
263                self.value_stack.push(val).unwrap();
264            }
265            f(self);
266            assert_eq!(self.value_stack.pop().ok(), Some(expected_result.into()));
267        }
268    }
269}