skrifa/outline/glyf/hint/engine/
mod.rs
1mod 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
38pub 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
100struct LoopBudget {
102 limit: usize,
105 backward_jumps: usize,
107 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 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 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 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 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}