skrifa/outline/glyf/hint/engine/
mod.rs1mod 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
36const MAX_RUN_INSTRUCTIONS: usize = 1_000_000;
40
41pub type OpResult = Result<(), HintErrorKind>;
42
43pub struct Engine<'a> {
45 program: ProgramState<'a>,
46 graphics: GraphicsState<'a>,
47 definitions: DefinitionState<'a>,
48 cvt: Cvt<'a>,
49 storage: Storage<'a>,
50 value_stack: ValueStack<'a>,
51 work_budget: WorkBudget,
52 axis_count: u16,
53 coords: &'a [F2Dot14],
54}
55
56impl<'a> Engine<'a> {
57 #[allow(clippy::too_many_arguments)]
58 pub fn new(
59 outlines: &Outlines,
60 program: ProgramState<'a>,
61 graphics: RetainedGraphicsState,
62 definitions: DefinitionState<'a>,
63 cvt: impl Into<Cvt<'a>>,
64 storage: impl Into<Storage<'a>>,
65 value_stack: ValueStack<'a>,
66 twilight: Zone<'a>,
67 glyph: Zone<'a>,
68 axis_count: u16,
69 coords: &'a [F2Dot14],
70 is_composite: bool,
71 ) -> Self {
72 let point_count = if glyph.points.is_empty() {
73 None
74 } else {
75 Some(glyph.points.len())
76 };
77 let graphics = GraphicsState {
78 retained: graphics,
79 zones: [twilight, glyph],
80 is_composite,
81 ..Default::default()
82 };
83 Self {
84 program,
85 graphics,
86 definitions,
87 cvt: cvt.into(),
88 storage: storage.into(),
89 value_stack,
90 work_budget: WorkBudget::new(outlines, point_count),
91 axis_count,
92 coords,
93 }
94 }
95
96 pub fn backward_compatibility(&self) -> bool {
97 self.graphics.backward_compatibility
98 }
99
100 pub fn retained_graphics_state(&self) -> &RetainedGraphicsState {
101 &self.graphics.retained
102 }
103}
104
105struct WorkBudget {
107 loop_limit: usize,
110 backward_jumps: usize,
112 loop_calls: usize,
114 skipped: usize,
116}
117
118impl WorkBudget {
119 fn new(outlines: &Outlines, point_count: Option<usize>) -> Self {
120 let cvt_len = outlines.cvt_len as usize;
121 let loop_limit = if let Some(point_count) = point_count {
124 (point_count * 10).max(50) + (cvt_len / 10).max(50)
125 } else {
126 300 + 22 * cvt_len
127 };
128 Self {
132 loop_limit,
133 backward_jumps: 0,
134 loop_calls: 0,
135 skipped: 0,
136 }
137 }
138
139 fn reset(&mut self) {
140 self.backward_jumps = 0;
141 self.loop_calls = 0;
142 self.skipped = 0;
143 }
144
145 fn doing_backward_jump(&mut self) -> Result<(), HintErrorKind> {
146 self.backward_jumps += 1;
147 if self.backward_jumps > self.loop_limit {
148 Err(HintErrorKind::ExceededExecutionBudget)
149 } else {
150 Ok(())
151 }
152 }
153
154 fn doing_loop_call(&mut self, count: usize) -> Result<(), HintErrorKind> {
155 self.loop_calls += count;
156 if self.loop_calls > self.loop_limit {
157 Err(HintErrorKind::ExceededExecutionBudget)
158 } else {
159 Ok(())
160 }
161 }
162
163 fn skipping_instruction(&mut self) -> Result<(), HintErrorKind> {
164 self.skipped += 1;
165 if self.skipped > MAX_RUN_INSTRUCTIONS {
166 Err(HintErrorKind::ExceededExecutionBudget)
167 } else {
168 Ok(())
169 }
170 }
171}
172
173#[cfg(test)]
174use mock::MockEngine;
175
176#[cfg(test)]
177mod mock {
178 use super::{
179 super::{
180 cow_slice::CowSlice,
181 definition::{Definition, DefinitionMap, DefinitionState},
182 program::{Program, ProgramState},
183 zone::Zone,
184 Point, PointFlags,
185 },
186 Engine, F26Dot6, GraphicsState, ValueStack, WorkBudget,
187 };
188
189 pub(super) struct MockEngine {
191 cvt_storage: Vec<i32>,
192 value_stack: Vec<i32>,
193 definitions: Vec<Definition>,
194 unscaled: Vec<Point<i32>>,
195 points: Vec<Point<F26Dot6>>,
196 point_flags: Vec<PointFlags>,
197 contours: Vec<u16>,
198 twilight: Vec<Point<F26Dot6>>,
199 twilight_flags: Vec<PointFlags>,
200 }
201
202 impl MockEngine {
203 pub fn new() -> Self {
204 Self {
205 cvt_storage: vec![0; 32],
206 value_stack: vec![0; 32],
207 definitions: vec![Default::default(); 8],
208 unscaled: vec![Default::default(); 32],
209 points: vec![Default::default(); 64],
210 point_flags: vec![Default::default(); 32],
211 contours: vec![31],
212 twilight: vec![Default::default(); 32],
213 twilight_flags: vec![Default::default(); 32],
214 }
215 }
216
217 pub fn engine(&mut self) -> Engine<'_> {
218 let font_code = &[];
219 let cv_code = &[];
220 let glyph_code = &[];
221 let (cvt, storage) = self.cvt_storage.split_at_mut(16);
222 let (function_defs, instruction_defs) = self.definitions.split_at_mut(5);
223 let definition = DefinitionState::new(
224 DefinitionMap::Mut(function_defs),
225 DefinitionMap::Mut(instruction_defs),
226 );
227 for (i, point) in self.unscaled.iter_mut().enumerate() {
228 let i = i as i32;
229 point.x = 57 + i * 2;
230 point.y = -point.x * 3;
231 }
232 let (points, original) = self.points.split_at_mut(32);
233 let glyph_zone = Zone::new(
234 &self.unscaled,
235 original,
236 points,
237 &mut self.point_flags,
238 &self.contours,
239 );
240 let (points, original) = self.twilight.split_at_mut(16);
241 let twilight_zone = Zone::new(&[], original, points, &mut self.twilight_flags, &[]);
242 let mut graphics_state = GraphicsState {
243 zones: [twilight_zone, glyph_zone],
244 ..Default::default()
245 };
246 graphics_state.update_projection_state();
247 Engine {
248 graphics: graphics_state,
249 cvt: CowSlice::new_mut(cvt).into(),
250 storage: CowSlice::new_mut(storage).into(),
251 value_stack: ValueStack::new(&mut self.value_stack, false),
252 program: ProgramState::new(font_code, cv_code, glyph_code, Program::Font),
253 work_budget: WorkBudget {
254 loop_limit: 10,
255 backward_jumps: 0,
256 loop_calls: 0,
257 skipped: 0,
258 },
259 definitions: definition,
260 axis_count: 0,
261 coords: &[],
262 }
263 }
264 }
265
266 impl Default for MockEngine {
267 fn default() -> Self {
268 Self::new()
269 }
270 }
271
272 impl Engine<'_> {
273 pub(super) fn test_exec(
276 &mut self,
277 push: &[i32],
278 expected_result: impl Into<i32>,
279 mut f: impl FnMut(&mut Engine),
280 ) {
281 for &val in push {
282 self.value_stack.push(val).unwrap();
283 }
284 f(self);
285 assert_eq!(self.value_stack.pop().ok(), Some(expected_result.into()));
286 }
287 }
288}