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

1//! Managing the graphics state.
2//!
3//! Implements 45 instructions.
4//!
5//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#managing-the-graphics-state>
6
7use super::{
8    super::{math, program::Program, round::RoundMode},
9    Engine, F26Dot6, HintErrorKind, OpResult, Point,
10};
11
12impl Engine<'_> {
13    /// Set vectors to coordinate axis.
14    ///
15    /// SVTCA\[a\] (0x00 - 0x01)
16    ///
17    /// Sets both the projection_vector and freedom_vector to the same one of
18    /// the coordinate axes.
19    ///
20    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom-and-projection-vectors-to-coordinate-axis>
21    ///
22    /// SPVTCA\[a\] (0x02 - 0x03)
23    ///
24    /// Sets the projection_vector to one of the coordinate axes depending on
25    /// the value of the flag a.
26    ///
27    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-projection_vector-to-coordinate-axis>
28    ///
29    /// SFVTCA\[a\] (0x04 - 0x05)
30    ///
31    /// Sets the freedom_vector to one of the coordinate axes depending on
32    /// the value of the flag a.
33    ///
34    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom_vector-to-coordinate-axis>
35    ///
36    /// FreeType combines these into a single function using some bit magic on
37    /// the opcode to determine which axes and vectors to set.
38    ///
39    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4051>
40    pub(super) fn op_svtca(&mut self, opcode: u8) -> OpResult {
41        let opcode = opcode as i32;
42        // The low bit of the opcode determines the axis to set (1 = x, 0 = y).
43        let x = (opcode & 1) << 14;
44        let y = x ^ 0x4000;
45        // Opcodes 0..4 set the projection vector.
46        if opcode < 4 {
47            self.graphics.proj_vector.x = x;
48            self.graphics.proj_vector.y = y;
49            self.graphics.dual_proj_vector.x = x;
50            self.graphics.dual_proj_vector.y = y;
51        }
52        // Opcodes with bit 2 unset modify the freedom vector.
53        if opcode & 2 == 0 {
54            self.graphics.freedom_vector.x = x;
55            self.graphics.freedom_vector.y = y;
56        }
57        self.graphics.update_projection_state();
58        Ok(())
59    }
60
61    /// Set vectors to line.
62    ///
63    /// SPVTL\[a\] (0x06 - 0x07)
64    ///
65    /// Sets the projection_vector to a unit vector parallel or perpendicular
66    /// to the line segment from point p1 to point p2.
67    ///
68    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-projection_vector-to-line>
69    ///
70    /// SFVTL\[a\] (0x08 - 0x09)
71    ///
72    /// Sets the freedom_vector to a unit vector parallel or perpendicular
73    /// to the line segment from point p1 to point p2.
74    ///
75    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom_vector-to-line>
76    ///
77    /// Pops: p1, p2 (point number)
78    ///
79    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3986>
80    pub(super) fn op_svtl(&mut self, opcode: u8) -> OpResult {
81        let index1 = self.value_stack.pop_usize()?;
82        let index2 = self.value_stack.pop_usize()?;
83        let is_parallel = opcode & 1 == 0;
84        let p1 = self.graphics.zp1().point(index2)?;
85        let p2 = self.graphics.zp2().point(index1)?;
86        let vector = line_vector(p1, p2, is_parallel);
87        if opcode < 8 {
88            self.graphics.proj_vector = vector;
89            self.graphics.dual_proj_vector = vector;
90        } else {
91            self.graphics.freedom_vector = vector;
92        }
93        self.graphics.update_projection_state();
94        Ok(())
95    }
96
97    /// Set freedom vector to projection vector.
98    ///
99    /// SFVTPV[] (0x0E)
100    ///
101    /// Sets the freedom_vector to be the same as the projection_vector.
102    ///
103    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom_vector-to-projection-vector>
104    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4128>
105    pub(super) fn op_sfvtpv(&mut self) -> OpResult {
106        self.graphics.freedom_vector = self.graphics.proj_vector;
107        self.graphics.update_projection_state();
108        Ok(())
109    }
110
111    /// Set dual projection vector to line.
112    ///
113    /// SDPVTL\[a\] (0x86 - 0x87)
114    ///
115    /// Pops: p1, p2 (point number)
116    ///
117    /// Pops two point numbers from the stack and uses them to specify a line
118    /// that defines a second, dual_projection_vector.
119    ///
120    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-dual-projection_vector-to-line>
121    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4663>
122    pub(super) fn op_sdpvtl(&mut self, opcode: u8) -> OpResult {
123        let index1 = self.value_stack.pop_usize()?;
124        let index2 = self.value_stack.pop_usize()?;
125        let is_parallel = opcode & 1 == 0;
126        // First set the dual projection vector from *original* points.
127        let p1 = self.graphics.zp1().original(index2)?;
128        let p2 = self.graphics.zp2().original(index1)?;
129        self.graphics.dual_proj_vector = line_vector(p1, p2, is_parallel);
130        // Now set the projection vector from the *current* points.
131        let p1 = self.graphics.zp1().point(index2)?;
132        let p2 = self.graphics.zp2().point(index1)?;
133        self.graphics.proj_vector = line_vector(p1, p2, is_parallel);
134        self.graphics.update_projection_state();
135        Ok(())
136    }
137
138    /// Set projection vector from stack.
139    ///
140    /// SPVFS[] (0x0A)
141    ///
142    /// Pops: y, x (2.14 fixed point numbers padded with zeroes)
143    ///
144    /// Sets the direction of the projection_vector, using values x and y taken
145    /// from the stack, so that its projections onto the x and y-axes are x and
146    /// y, which are specified as signed (two’s complement) fixed-point (2.14)
147    /// numbers. The square root of (x2 + y2) must be equal to 0x4000 (hex)
148    ///
149    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-projection_vector-from-stack>
150    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4142>
151    pub(super) fn op_spvfs(&mut self) -> OpResult {
152        let y = self.value_stack.pop()? as i16 as i32;
153        let x = self.value_stack.pop()? as i16 as i32;
154        let vector = if (x, y) == (0, 0) {
155            self.graphics.proj_vector
156        } else {
157            math::normalize14(x, y)
158        };
159        self.graphics.proj_vector = vector;
160        self.graphics.dual_proj_vector = vector;
161        self.graphics.update_projection_state();
162        Ok(())
163    }
164
165    /// Set freedom vector from stack.
166    ///
167    /// SFVFS[] (0x0B)
168    ///
169    /// Pops: y, x (2.14 fixed point numbers padded with zeroes)
170    ///
171    /// Sets the direction of the freedom_vector, using values x and y taken
172    /// from the stack, so that its projections onto the x and y-axes are x and
173    /// y, which are specified as signed (two’s complement) fixed-point (2.14)
174    /// numbers. The square root of (x2 + y2) must be equal to 0x4000 (hex)
175    ///
176    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom_vector-from-stack>
177    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4169>
178    pub(super) fn op_sfvfs(&mut self) -> OpResult {
179        let y = self.value_stack.pop()? as i16 as i32;
180        let x = self.value_stack.pop()? as i16 as i32;
181        let vector = if (x, y) == (0, 0) {
182            self.graphics.freedom_vector
183        } else {
184            math::normalize14(x, y)
185        };
186        self.graphics.freedom_vector = vector;
187        self.graphics.update_projection_state();
188        Ok(())
189    }
190
191    /// Get projection vector.
192    ///
193    /// GPV[] (0x0C)
194    ///
195    /// Pushes: x, y (2.14 fixed point numbers padded with zeroes)
196    ///
197    /// Pushes the x and y components of the projection_vector onto the stack
198    /// as two 2.14 numbers.
199    ///
200    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-projection_vector>
201    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4194>
202    pub(super) fn op_gpv(&mut self) -> OpResult {
203        let vector = self.graphics.proj_vector;
204        self.value_stack.push(vector.x)?;
205        self.value_stack.push(vector.y)
206    }
207
208    /// Get freedom vector.
209    ///
210    /// GFV[] (0x0D)
211    ///
212    /// Pushes: x, y (2.14 fixed point numbers padded with zeroes)
213    ///
214    /// Pushes the x and y components of the freedom_vector onto the stack as
215    /// two 2.14 numbers.
216    ///
217    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-freedom_vector>
218    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4209>
219    pub(super) fn op_gfv(&mut self) -> OpResult {
220        let vector = self.graphics.freedom_vector;
221        self.value_stack.push(vector.x)?;
222        self.value_stack.push(vector.y)
223    }
224
225    /// Set reference point 0.
226    ///
227    /// SRP0[] (0x10)
228    ///
229    /// Pops: p (point number)
230    ///
231    /// Pops a point number from the stack and sets rp0 to that point number.
232    ///
233    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-reference-point-0>
234    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4224>
235    pub(super) fn op_srp0(&mut self) -> OpResult {
236        let p = self.value_stack.pop_usize()?;
237        self.graphics.rp0 = p;
238        Ok(())
239    }
240
241    /// Set reference point 1.
242    ///
243    /// SRP1[] (0x11)
244    ///
245    /// Pops: p (point number)
246    ///
247    /// Pops a point number from the stack and sets rp1 to that point number.
248    ///
249    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-reference-point-1>
250    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4238>
251    pub(super) fn op_srp1(&mut self) -> OpResult {
252        let p = self.value_stack.pop_usize()?;
253        self.graphics.rp1 = p;
254        Ok(())
255    }
256
257    /// Set reference point 2.
258    ///
259    /// SRP2[] (0x12)
260    ///
261    /// Pops: p (point number)
262    ///
263    /// Pops a point number from the stack and sets rp2 to that point number.
264    ///
265    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-reference-point-2>
266    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4252>
267    pub(super) fn op_srp2(&mut self) -> OpResult {
268        let p = self.value_stack.pop_usize()?;
269        self.graphics.rp2 = p;
270        Ok(())
271    }
272
273    /// Set zone pointer 0.
274    ///
275    /// SZP0[] (0x13)
276    ///
277    /// Pops: n (zone number)
278    ///
279    /// Pops a zone number, n, from the stack and sets zp0 to the zone with
280    /// that number. If n is 0, zp0 points to zone 0. If n is 1, zp0 points
281    /// to zone 1. Any other value for n is an error.
282    ///
283    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-zone-pointer-0>
284    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4746>
285    pub(super) fn op_szp0(&mut self) -> OpResult {
286        let n = self.value_stack.pop()?;
287        self.graphics.zp0 = n.try_into()?;
288        Ok(())
289    }
290
291    /// Set zone pointer 1.
292    ///
293    /// SZP1[] (0x14)
294    ///
295    /// Pops: n (zone number)
296    ///
297    /// Pops a zone number, n, from the stack and sets zp0 to the zone with
298    /// that number. If n is 0, zp1 points to zone 0. If n is 1, zp0 points
299    /// to zone 1. Any other value for n is an error.
300    ///
301    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-zone-pointer-1>
302    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4776>
303    pub(super) fn op_szp1(&mut self) -> OpResult {
304        let n = self.value_stack.pop()?;
305        self.graphics.zp1 = n.try_into()?;
306        Ok(())
307    }
308
309    /// Set zone pointer 2.
310    ///
311    /// SZP2[] (0x15)
312    ///
313    /// Pops: n (zone number)
314    ///
315    /// Pops a zone number, n, from the stack and sets zp0 to the zone with
316    /// that number. If n is 0, zp2 points to zone 0. If n is 1, zp0 points
317    /// to zone 1. Any other value for n is an error.
318    ///
319    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-zone-pointer-2>
320    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4806>
321    pub(super) fn op_szp2(&mut self) -> OpResult {
322        let n = self.value_stack.pop()?;
323        self.graphics.zp2 = n.try_into()?;
324        Ok(())
325    }
326
327    /// Set zone pointers.
328    ///
329    /// SZPS[] (0x16)
330    ///
331    /// Pops: n (zone number)
332    ///
333    /// Pops a zone number from the stack and sets all of the zone pointers to
334    /// point to the zone with that number. If n is 0, all three zone pointers
335    /// will point to zone 0. If n is 1, all three zone pointers will point to
336    /// zone 1. Any other value for n is an error.
337    ///
338    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-zone-pointers>
339    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4836>
340    pub(super) fn op_szps(&mut self) -> OpResult {
341        let n = self.value_stack.pop()?;
342        let zp = n.try_into()?;
343        self.graphics.zp0 = zp;
344        self.graphics.zp1 = zp;
345        self.graphics.zp2 = zp;
346        Ok(())
347    }
348
349    /// Round to half grid.
350    ///
351    /// RTHG[] (0x19)
352    ///
353    /// Sets the round_state variable to state 0 (hg). In this state, the
354    /// coordinates of a point are rounded to the nearest half grid line.
355    ///
356    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-to-half-grid>
357    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4393>
358    pub(super) fn op_rthg(&mut self) -> OpResult {
359        self.graphics.round_state.mode = RoundMode::HalfGrid;
360        Ok(())
361    }
362
363    /// Round to grid.
364    ///
365    /// RTG[] (0x18)
366    ///
367    /// Sets the round_state variable to state 1 (g). In this state, distances
368    /// are rounded to the closest grid line.
369    ///
370    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-to-grid>
371    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4407>
372    pub(super) fn op_rtg(&mut self) -> OpResult {
373        self.graphics.round_state.mode = RoundMode::Grid;
374        Ok(())
375    }
376
377    /// Round to double grid.
378    ///
379    /// RTDG[] (0x3D)
380    ///
381    /// Sets the round_state variable to state 2 (dg). In this state, distances
382    /// are rounded to the closest half or integer pixel.
383    ///
384    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-to-double-grid>
385    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4420>
386    pub(super) fn op_rtdg(&mut self) -> OpResult {
387        self.graphics.round_state.mode = RoundMode::DoubleGrid;
388        Ok(())
389    }
390
391    /// Round down to grid.
392    ///
393    /// RDTG[] (0x7D)
394    ///
395    /// Sets the round_state variable to state 3 (dtg). In this state, distances
396    /// are rounded down to the closest integer grid line.
397    ///
398    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-down-to-grid>
399    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4447>
400    pub(super) fn op_rdtg(&mut self) -> OpResult {
401        self.graphics.round_state.mode = RoundMode::DownToGrid;
402        Ok(())
403    }
404
405    /// Round up to grid.
406    ///
407    /// RUTG[] (0x7C)
408    ///
409    /// Sets the round_state variable to state 4 (utg). In this state distances
410    /// are rounded up to the closest integer pixel boundary.
411    ///
412    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-up-to-grid>
413    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4433>
414    pub(super) fn op_rutg(&mut self) -> OpResult {
415        self.graphics.round_state.mode = RoundMode::UpToGrid;
416        Ok(())
417    }
418
419    /// Round off.
420    ///
421    /// ROFF[] (0x7A)
422    ///
423    /// Sets the round_state variable to state 5 (off). In this state rounding
424    /// is turned off.
425    ///
426    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-off>
427    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4461>
428    pub(super) fn op_roff(&mut self) -> OpResult {
429        self.graphics.round_state.mode = RoundMode::Off;
430        Ok(())
431    }
432
433    /// Super round.
434    ///
435    /// SROUND[] (0x76)
436    ///
437    /// Pops: n (number decomposed to obtain period, phase threshold)
438    ///
439    /// SROUND allows you fine control over the effects of the round_state
440    /// variable by allowing you to set the values of three components of
441    /// the round_state: period, phase, and threshold.
442    ///
443    /// More formally, SROUND maps the domain of 26.6 fixed point numbers into
444    /// a set of discrete values that are separated by equal distances.
445    ///
446    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#super-round>
447    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4475>
448    pub(super) fn op_sround(&mut self) -> OpResult {
449        let n = self.value_stack.pop()?;
450        self.super_round(0x4000, n);
451        self.graphics.round_state.mode = RoundMode::Super;
452        Ok(())
453    }
454
455    /// Super round 45 degrees.
456    ///
457    /// S45ROUND[] (0x77)
458    ///
459    /// Pops: n (number decomposed to obtain period, phase threshold)
460    ///
461    /// S45ROUND is analogous to SROUND. The gridPeriod is SQRT(2)/2 pixels
462    /// rather than 1 pixel. It is useful for measuring at a 45 degree angle
463    /// with the coordinate axes.
464    ///
465    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#super-round-45-degrees>
466    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4492>
467    pub(super) fn op_s45round(&mut self) -> OpResult {
468        let n = self.value_stack.pop()?;
469        self.super_round(0x2D41, n);
470        self.graphics.round_state.mode = RoundMode::Super45;
471        Ok(())
472    }
473
474    /// Helper function for decomposing period, phase and threshold for
475    /// the SROUND[] and SROUND45[] instructions.
476    ///
477    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2299>
478    fn super_round(&mut self, grid_period: i32, selector: i32) {
479        let round_state = &mut self.graphics.round_state;
480        let period = match selector & 0xC0 {
481            0 => grid_period / 2,
482            0x40 => grid_period,
483            0x80 => grid_period * 2,
484            0xC0 => grid_period,
485            _ => round_state.period,
486        };
487        let phase = match selector & 0x30 {
488            0 => 0,
489            0x10 => period / 4,
490            0x20 => period / 2,
491            0x30 => period * 3 / 4,
492            _ => round_state.phase,
493        };
494        let threshold = if (selector & 0x0F) == 0 {
495            period - 1
496        } else {
497            ((selector & 0x0F) - 4) * period / 8
498        };
499        round_state.period = period >> 8;
500        round_state.phase = phase >> 8;
501        round_state.threshold = threshold >> 8;
502    }
503
504    /// Set loop variable.
505    ///
506    /// SLOOP[] (0x17)
507    ///
508    /// Pops: n (value for loop Graphics State variable (integer))
509    ///
510    /// Pops a value, n, from the stack and sets the loop variable count to
511    /// that value. The loop variable works with the SHP\[a\], SHPIX[], IP[],
512    /// FLIPPT[], and ALIGNRP[]. The value n indicates the number of times
513    /// the instruction is to be repeated. After the instruction executes,
514    /// the loop variable is reset to 1.
515    ///
516    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-loop-variable>
517    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3287>
518    pub(super) fn op_sloop(&mut self) -> OpResult {
519        let n = self.value_stack.pop()?;
520        if n < 0 {
521            return Err(HintErrorKind::NegativeLoopCounter);
522        }
523        // As in FreeType, heuristically limit the number of loops to 16 bits.
524        self.graphics.loop_counter = (n as u32).min(0xFFFF);
525        Ok(())
526    }
527
528    /// Set minimum distance.
529    ///
530    /// SMD[] (0x1A)
531    ///
532    /// Pops: distance: value for minimum_distance (F26Dot6)
533    ///
534    /// Pops a value from the stack and sets the minimum_distance variable
535    /// to that value. The distance is assumed to be expressed in sixty-fourths
536    /// of a pixel.
537    ///
538    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-minimum_distance>
539    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4266>
540    pub(super) fn op_smd(&mut self) -> OpResult {
541        let distance = self.value_stack.pop_f26dot6()?;
542        self.graphics.min_distance = distance;
543        Ok(())
544    }
545
546    /// Instruction execution control.
547    ///
548    /// INSTCTRL[] (0x8E)
549    ///
550    /// Pops: s: selector flag (int32)
551    ///       value: used to set value of instruction_control (uint16 padded)
552    ///
553    /// Sets the instruction control state variable making it possible to turn
554    /// on or off the execution of instructions and to regulate use of
555    /// parameters set in the CVT program. INSTCTRL[ ] can only be executed in
556    /// the CVT program.
557    ///
558    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#instruction-execution-control>
559    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4871>
560    pub(super) fn op_instctrl(&mut self) -> OpResult {
561        let selector = self.value_stack.pop()? as u32;
562        let value = self.value_stack.pop()? as u32;
563        // Selectors are indices starting with 1; not flags.
564        // Avoid potential subtract with overflow below.
565        // See <https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=70426>
566        // and <https://oss-fuzz.com/testcase?key=6243979511791616>
567        if !(1..=3).contains(&selector) {
568            return Ok(());
569        }
570        // Convert index to flag.
571        let selector_flag = 1 << (selector - 1);
572        if value != 0 && value != selector_flag {
573            return Ok(());
574        }
575        // If preserving linear metrics, prevent modification of the backward
576        // compatibility flag.
577        if selector == 3 && self.graphics.target.preserve_linear_metrics() {
578            return Ok(());
579        }
580        match (self.program.initial, selector) {
581            // Typically, this instruction can only be executed in the prep table.
582            (Program::ControlValue, _) => {
583                self.graphics.instruct_control &= !(selector_flag as u8);
584                self.graphics.instruct_control |= value as u8;
585            }
586            // Allow an exception in the glyph program for selector 3 which can
587            // temporarily disable backward compatibility mode.
588            (Program::Glyph, 3) => {
589                self.graphics.backward_compatibility = value != 4;
590            }
591            _ => {}
592        }
593        Ok(())
594    }
595
596    /// Scan conversion control.
597    ///
598    /// SCANCTRL[] (0x85)
599    ///
600    /// Pops: n: flags indicating when to turn on dropout control mode
601    ///
602    /// SCANCTRL is used to set the value of the Graphics State variable
603    /// scan_control which in turn determines whether the scan converter
604    /// will activate dropout control for this glyph.
605    ///
606    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#scan-conversion-control>
607    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4933>
608    pub(super) fn op_scanctrl(&mut self) -> OpResult {
609        let n = self.value_stack.pop()?;
610        // Bits 0-7 represent the threshold value for ppem.
611        let threshold = n & 0xFF;
612        match threshold {
613            // A value of FF in bits 0-7 means invoke scan_control for all
614            // sizes.
615            0xFF => self.graphics.scan_control = true,
616            // A value of 0 in bits 0-7 means never invoke scan_control.
617            0 => self.graphics.scan_control = false,
618            _ => {
619                let ppem = self.graphics.ppem;
620                let is_rotated = self.graphics.is_rotated;
621                let is_stretched = self.graphics.is_stretched;
622                let scan_control = &mut self.graphics.scan_control;
623                // Bits 8-13 are used to turn on scan_control in cases where
624                // the specified conditions are met. Bits 8, 9 and 10 are used
625                // to turn on the scan_control mode (assuming other
626                // conditions do not block it). Bits 11, 12, and 13 are used to
627                // turn off the dropout mode unless other conditions force it
628                if (n & 0x100) != 0 && ppem <= threshold {
629                    // Bit 8: Set scan_control to TRUE if other conditions
630                    // do not block and ppem is less than or equal to the
631                    // threshold value.
632                    *scan_control = true;
633                }
634                if (n & 0x200) != 0 && is_rotated {
635                    // Bit 9: Set scan_control to TRUE if other conditions
636                    // do not block and the glyph is rotated
637                    *scan_control = true;
638                }
639                if (n & 0x400) != 0 && is_stretched {
640                    // Bit 10: Set scan_control to TRUE if other conditions
641                    // do not block and the glyph is stretched.
642                    *scan_control = true;
643                }
644                if (n & 0x800) != 0 && ppem > threshold {
645                    // Bit 11: Set scan_control to FALSE unless ppem is less
646                    // than or equal to the threshold value.
647                    *scan_control = false;
648                }
649                if (n & 0x1000) != 0 && is_rotated {
650                    // Bit 12: Set scan_control to FALSE based on rotation
651                    // state.
652                    *scan_control = false;
653                }
654                if (n & 0x2000) != 0 && is_stretched {
655                    // Bit 13: Set scan_control to FALSE based on stretched
656                    // state.
657                    *scan_control = false;
658                }
659            }
660        }
661        Ok(())
662    }
663
664    /// Scan type.
665    ///
666    /// SCANTYPE[] (0x8D)
667    ///
668    /// Pops: n: 16 bit integer
669    ///
670    /// Pops a 16-bit integer whose value is used to determine which rules the
671    /// scan converter will use.
672    ///
673    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#scantype>
674    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4980>
675    pub(super) fn op_scantype(&mut self) -> OpResult {
676        let n = self.value_stack.pop()?;
677        self.graphics.scan_type = n & 0xFFFF;
678        Ok(())
679    }
680
681    /// Set control value table cut in.
682    ///
683    /// SCVTCI[] (0x1D)
684    ///
685    /// Pops: n: value for cut_in (F26Dot6)
686    ///
687    /// Sets the control_value_cut_in in the Graphics State. The value n is
688    /// expressed in sixty-fourths of a pixel.
689    ///
690    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-control-value-table-cut-in>
691    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4280>
692    pub(super) fn op_scvtci(&mut self) -> OpResult {
693        let n = self.value_stack.pop_f26dot6()?;
694        self.graphics.control_value_cutin = n;
695        Ok(())
696    }
697
698    /// Set single width cut in.
699    ///
700    /// SSWCI[] (0x1E)
701    ///
702    /// Pops: n: value for single_width_cut_in (F26Dot6)
703    ///
704    /// Sets the single_width_cut_in in the Graphics State. The value n is
705    /// expressed in sixty-fourths of a pixel.
706    ///
707    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-single_width_cut_in>
708    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4294>
709    pub(super) fn op_sswci(&mut self) -> OpResult {
710        let n = self.value_stack.pop_f26dot6()?;
711        self.graphics.single_width_cutin = n;
712        Ok(())
713    }
714
715    /// Set single width.
716    ///
717    /// SSW[] (0x1F)
718    ///
719    /// Pops: n: value for single_width_value (FUnits)
720    ///
721    /// Sets the single_width_value in the Graphics State. The
722    /// single_width_value is expressed in FUnits, which the
723    /// interpreter converts to pixels (F26Dot6).
724    ///
725    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-single-width>
726    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4308>
727    pub(super) fn op_ssw(&mut self) -> OpResult {
728        let n = self.value_stack.pop()?;
729        self.graphics.single_width = F26Dot6::from_bits(math::mul(n, self.graphics.scale));
730        Ok(())
731    }
732
733    /// Set auto flip on.
734    ///
735    /// FLIPON[] (0x4D)
736    ///
737    /// Sets the auto_flip Boolean in the Graphics State to TRUE causing the
738    /// MIRP instructions to ignore the sign of Control Value Table entries.
739    /// The default auto_flip Boolean value is TRUE.
740    ///
741    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-the-auto_flip-boolean-to-on>
742    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4323>
743    pub(super) fn op_flipon(&mut self) -> OpResult {
744        self.graphics.auto_flip = true;
745        Ok(())
746    }
747
748    /// Set auto flip off.
749    ///
750    /// FLIPOFF[] (0x4E)
751    ///
752    /// Set the auto_flip Boolean in the Graphics State to FALSE causing the
753    /// MIRP instructions to use the sign of Control Value Table entries.
754    /// The default auto_flip Boolean value is TRUE.
755    ///
756    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-the-auto_flip-boolean-to-off>
757    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4336>
758    pub(super) fn op_flipoff(&mut self) -> OpResult {
759        self.graphics.auto_flip = false;
760        Ok(())
761    }
762
763    /// Set angle weight.
764    ///
765    /// SANGW[] (0x7E)
766    ///
767    /// Pops: weight: value for angle_weight
768    ///
769    /// SANGW is no longer needed because of dropped support to the AA
770    /// (Adjust Angle) instruction. AA was the only instruction that used
771    /// angle_weight in the global graphics state.
772    ///
773    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-angle_weight>
774    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4349>
775    pub(super) fn op_sangw(&mut self) -> OpResult {
776        // totally unsupported but we still need to pop the stack value
777        let _weight = self.value_stack.pop()?;
778        Ok(())
779    }
780
781    /// Set delta base in graphics state.
782    ///
783    /// SDB[] (0x5E)
784    ///
785    /// Pops: n: value for delta_base
786    ///
787    /// Pops a number, n, and sets delta_base to the value n. The default for
788    /// delta_base is 9.
789    ///
790    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-delta_base-in-the-graphics-state>
791    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4362>
792    pub(super) fn op_sdb(&mut self) -> OpResult {
793        let n = self.value_stack.pop()?;
794        self.graphics.delta_base = n as u16;
795        Ok(())
796    }
797
798    /// Set delta shift in graphics state.
799    ///
800    /// SDS[] (0x5F)
801    ///
802    /// Pops: n: value for delta_shift
803    ///
804    /// Sets delta_shift to the value n. The default for delta_shift is 3.
805    ///
806    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-delta_shift-in-the-graphics-state>
807    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4376>
808    pub(super) fn op_sds(&mut self) -> OpResult {
809        let n = self.value_stack.pop()?;
810        if n as u32 > 6 {
811            Err(HintErrorKind::InvalidStackValue(n))
812        } else {
813            self.graphics.delta_shift = n as u16;
814            Ok(())
815        }
816    }
817}
818
819/// Computes a parallel or perpendicular normalized vector for the line
820/// between the two given points.
821///
822/// This is common code for the "set vector to line" instructions.
823///
824/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4009>
825fn line_vector(p1: Point<F26Dot6>, p2: Point<F26Dot6>, is_parallel: bool) -> Point<i32> {
826    let mut a = (p1.x - p2.x).to_bits();
827    let mut b = (p1.y - p2.y).to_bits();
828    if a == 0 && b == 0 {
829        // If the points are equal, set to the x axis.
830        a = 0x4000;
831    } else if !is_parallel {
832        // Perform a counter-clockwise rotation by 90 degrees to form a
833        // perpendicular line.
834        let c = b;
835        b = a;
836        a = -c;
837    }
838    math::normalize14(a, b)
839}
840
841#[cfg(test)]
842mod tests {
843    use super::{
844        super::{
845            super::zone::{Zone, ZonePointer},
846            math, F2Dot14, MockEngine,
847        },
848        F26Dot6, HintErrorKind, Point, Program, RoundMode,
849    };
850
851    // Some helpful constants for testing vectors
852    const ONE: i32 = F2Dot14::ONE.to_bits() as i32;
853
854    const X_AXIS: Point<i32> = Point::new(ONE, 0);
855    const Y_AXIS: Point<i32> = Point::new(0, ONE);
856
857    #[test]
858    fn set_vectors_to_coord_axis() {
859        let mut mock = MockEngine::new();
860        let mut engine = mock.engine();
861        // freedom and projection vector to y axis
862        engine.op_svtca(0x00).unwrap();
863        assert_eq!(engine.graphics.freedom_vector, Y_AXIS);
864        assert_eq!(engine.graphics.proj_vector, Y_AXIS);
865        // freedom and projection vector to x axis
866        engine.op_svtca(0x01).unwrap();
867        assert_eq!(engine.graphics.freedom_vector, X_AXIS);
868        assert_eq!(engine.graphics.proj_vector, X_AXIS);
869        // projection vector to y axis
870        engine.op_svtca(0x02).unwrap();
871        assert_eq!(engine.graphics.proj_vector, Y_AXIS);
872        // projection vector to x axis
873        engine.op_svtca(0x03).unwrap();
874        assert_eq!(engine.graphics.proj_vector, X_AXIS);
875        // freedom vector to y axis
876        engine.op_svtca(0x04).unwrap();
877        assert_eq!(engine.graphics.freedom_vector, Y_AXIS);
878        // freedom vector to x axis
879        engine.op_svtca(0x05).unwrap();
880        assert_eq!(engine.graphics.freedom_vector, X_AXIS);
881    }
882
883    #[test]
884    fn set_get_vectors_from_stack() {
885        let mut mock = MockEngine::new();
886        let mut engine = mock.engine();
887        // projection vector
888        engine.value_stack.push(X_AXIS.x).unwrap();
889        engine.value_stack.push(X_AXIS.y).unwrap();
890        engine.op_spvfs().unwrap();
891        assert_eq!(engine.graphics.proj_vector, X_AXIS);
892        engine.op_gpv().unwrap();
893        let y = engine.value_stack.pop().unwrap();
894        let x = engine.value_stack.pop().unwrap();
895        assert_eq!(Point::new(x, y), X_AXIS);
896        // freedom vector
897        engine.value_stack.push(Y_AXIS.x).unwrap();
898        engine.value_stack.push(Y_AXIS.y).unwrap();
899        engine.op_sfvfs().unwrap();
900        assert_eq!(engine.graphics.freedom_vector, Y_AXIS);
901        engine.op_gfv().unwrap();
902        let y = engine.value_stack.pop().unwrap();
903        let x = engine.value_stack.pop().unwrap();
904        assert_eq!(Point::new(x, y), Y_AXIS);
905    }
906
907    #[test]
908    fn set_vectors_to_line() {
909        let mut mock = MockEngine::new();
910        let mut engine = mock.engine();
911        // Set up a zone for testing and set all the zone pointers to it.
912        let points = &mut [Point::new(0, 0), Point::new(64, 0)].map(|p| p.map(F26Dot6::from_bits));
913        let original =
914            &mut [Point::new(0, 64), Point::new(0, -64)].map(|p| p.map(F26Dot6::from_bits));
915        engine.graphics.zones[1] = Zone {
916            points,
917            original,
918            unscaled: &mut [],
919            flags: &mut [],
920            contours: &[],
921        };
922        engine.value_stack.push(1).unwrap();
923        engine.op_szps().unwrap();
924        // First, push point indices (a few times for reuse)
925        for _ in 0..6 {
926            engine.value_stack.push(1).unwrap();
927            engine.value_stack.push(0).unwrap();
928        }
929        // SPVTL: set projection vector to line:
930        {
931            // (parallel)
932            engine.op_svtl(0x6).unwrap();
933            assert_eq!(engine.graphics.proj_vector, X_AXIS);
934            // (perpendicular)
935            engine.op_svtl(0x7).unwrap();
936            assert_eq!(engine.graphics.proj_vector, Point::new(0, ONE));
937        }
938        // SFVTL: set freedom vector to line:
939        {
940            // (parallel)
941            engine.op_svtl(0x8).unwrap();
942            assert_eq!(engine.graphics.freedom_vector, X_AXIS);
943            // (perpendicular)
944            engine.op_svtl(0x9).unwrap();
945            assert_eq!(engine.graphics.freedom_vector, Point::new(0, ONE));
946        }
947        // SDPVTL: set dual projection vector to line:
948        {
949            // (parallel)
950            engine.op_sdpvtl(0x86).unwrap();
951            assert_eq!(engine.graphics.dual_proj_vector, Point::new(0, -ONE));
952            // (perpendicular)
953            engine.op_sdpvtl(0x87).unwrap();
954            assert_eq!(engine.graphics.dual_proj_vector, Point::new(ONE, 0));
955        }
956    }
957
958    /// Lots of little tests for instructions that just set fields on
959    /// the graphics state.
960    #[test]
961    fn simple_state_setting() {
962        let mut mock = MockEngine::new();
963        let mut engine = mock.engine();
964        // srp0
965        engine.value_stack.push(111).unwrap();
966        engine.op_srp0().unwrap();
967        assert_eq!(engine.graphics.rp0, 111);
968        // srp1
969        engine.value_stack.push(222).unwrap();
970        engine.op_srp1().unwrap();
971        assert_eq!(engine.graphics.rp1, 222);
972        // srp2
973        engine.value_stack.push(333).unwrap();
974        engine.op_srp2().unwrap();
975        assert_eq!(engine.graphics.rp2, 333);
976        // zp0
977        engine.value_stack.push(1).unwrap();
978        engine.op_szp0().unwrap();
979        assert_eq!(engine.graphics.zp0, ZonePointer::Glyph);
980        // zp1
981        engine.value_stack.push(0).unwrap();
982        engine.op_szp1().unwrap();
983        assert_eq!(engine.graphics.zp1, ZonePointer::Twilight);
984        // zp2
985        engine.value_stack.push(1).unwrap();
986        engine.op_szp2().unwrap();
987        assert_eq!(engine.graphics.zp2, ZonePointer::Glyph);
988        // zps
989        engine.value_stack.push(0).unwrap();
990        engine.op_szps().unwrap();
991        assert_eq!(
992            [
993                engine.graphics.zp0,
994                engine.graphics.zp1,
995                engine.graphics.zp2
996            ],
997            [ZonePointer::Twilight; 3]
998        );
999        // zp failure
1000        engine.value_stack.push(2).unwrap();
1001        assert!(matches!(
1002            engine.op_szps(),
1003            Err(HintErrorKind::InvalidZoneIndex(2))
1004        ));
1005        // rtg
1006        engine.op_rtg().unwrap();
1007        assert_eq!(engine.graphics.round_state.mode, RoundMode::Grid);
1008        // rtdg
1009        engine.op_rtdg().unwrap();
1010        assert_eq!(engine.graphics.round_state.mode, RoundMode::DoubleGrid);
1011        // rdtg
1012        engine.op_rdtg().unwrap();
1013        assert_eq!(engine.graphics.round_state.mode, RoundMode::DownToGrid);
1014        // rutg
1015        engine.op_rutg().unwrap();
1016        assert_eq!(engine.graphics.round_state.mode, RoundMode::UpToGrid);
1017        // roff
1018        engine.op_roff().unwrap();
1019        assert_eq!(engine.graphics.round_state.mode, RoundMode::Off);
1020        // sround
1021        engine.value_stack.push(0).unwrap();
1022        engine.op_sround().unwrap();
1023        assert_eq!(engine.graphics.round_state.mode, RoundMode::Super);
1024        // s45round
1025        engine.value_stack.push(0).unwrap();
1026        engine.op_s45round().unwrap();
1027        assert_eq!(engine.graphics.round_state.mode, RoundMode::Super45);
1028        // sloop
1029        engine.value_stack.push(10).unwrap();
1030        engine.op_sloop().unwrap();
1031        assert_eq!(engine.graphics.loop_counter, 10);
1032        // loop variable cannot be negative
1033        engine.value_stack.push(-10).unwrap();
1034        assert!(matches!(
1035            engine.op_sloop(),
1036            Err(HintErrorKind::NegativeLoopCounter)
1037        ));
1038        // smd
1039        engine.value_stack.push(64).unwrap();
1040        engine.op_smd().unwrap();
1041        assert_eq!(engine.graphics.min_distance, F26Dot6::from_bits(64));
1042        // scantype
1043        engine.value_stack.push(50).unwrap();
1044        engine.op_scantype().unwrap();
1045        assert_eq!(engine.graphics.scan_type, 50);
1046        // scvtci
1047        engine.value_stack.push(128).unwrap();
1048        engine.op_scvtci().unwrap();
1049        assert_eq!(engine.graphics.control_value_cutin, F26Dot6::from_bits(128));
1050        // sswci
1051        engine.value_stack.push(100).unwrap();
1052        engine.op_sswci().unwrap();
1053        assert_eq!(engine.graphics.single_width_cutin, F26Dot6::from_bits(100));
1054        // ssw
1055        engine.graphics.scale = 64;
1056        engine.value_stack.push(100).unwrap();
1057        engine.op_ssw().unwrap();
1058        assert_eq!(
1059            engine.graphics.single_width,
1060            F26Dot6::from_bits(math::mul(100, engine.graphics.scale))
1061        );
1062        // flipoff
1063        engine.op_flipoff().unwrap();
1064        assert!(!engine.graphics.auto_flip);
1065        // flipon
1066        engine.op_flipon().unwrap();
1067        assert!(engine.graphics.auto_flip);
1068        // sdb
1069        engine.value_stack.push(172).unwrap();
1070        engine.op_sdb().unwrap();
1071        assert_eq!(engine.graphics.delta_base, 172);
1072        // sds
1073        engine.value_stack.push(4).unwrap();
1074        engine.op_sds().unwrap();
1075        assert_eq!(engine.graphics.delta_shift, 4);
1076        // delta_shift has a max value of 6
1077        engine.value_stack.push(7).unwrap();
1078        assert!(matches!(
1079            engine.op_sds(),
1080            Err(HintErrorKind::InvalidStackValue(7))
1081        ));
1082    }
1083
1084    #[test]
1085    fn instctrl() {
1086        let mut mock = MockEngine::new();
1087        let mut engine = mock.engine();
1088        engine.program.initial = Program::ControlValue;
1089        // selectors 1..=3 are valid and values for each selector
1090        // can be 0, which disables the field, or 1 << (selector - 1) to
1091        // enable it
1092        for selector in 1..=3 {
1093            // enable first
1094            let enable_mask = (1 << (selector - 1)) as u8;
1095            engine.value_stack.push(enable_mask as i32).unwrap();
1096            engine.value_stack.push(selector).unwrap();
1097            engine.op_instctrl().unwrap();
1098            assert!(engine.graphics.instruct_control & enable_mask != 0);
1099            // now disable
1100            engine.value_stack.push(0).unwrap();
1101            engine.value_stack.push(selector).unwrap();
1102            engine.op_instctrl().unwrap();
1103            assert!(engine.graphics.instruct_control & enable_mask == 0);
1104        }
1105        // in glyph programs, selector 3 can be used to toggle
1106        // backward_compatibility
1107        engine.program.initial = Program::Glyph;
1108        // enabling this flag opts into "native ClearType mode"
1109        // which disables backward compatibility
1110        engine.value_stack.push((3 - 1) << 1).unwrap();
1111        engine.value_stack.push(3).unwrap();
1112        engine.op_instctrl().unwrap();
1113        assert!(!engine.graphics.backward_compatibility);
1114        // and disabling it enables backward compatibility
1115        engine.value_stack.push(0).unwrap();
1116        engine.value_stack.push(3).unwrap();
1117        engine.op_instctrl().unwrap();
1118        assert!(engine.graphics.backward_compatibility);
1119    }
1120
1121    // Subtract with overflow caught by fuzzing when selector == 0
1122    // See <https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=70426>
1123    // and <https://oss-fuzz.com/testcase?key=6243979511791616>
1124    #[test]
1125    fn instctrl_avoid_overflow() {
1126        let mut mock = MockEngine::new();
1127        let mut engine = mock.engine();
1128        engine.program.initial = Program::ControlValue;
1129        engine.value_stack.push(0).unwrap();
1130        engine.value_stack.push(0).unwrap();
1131        engine.op_instctrl().unwrap();
1132    }
1133
1134    #[test]
1135    fn scanctrl() {
1136        let mut mock = MockEngine::new();
1137        let mut engine = mock.engine();
1138        // Example modes from specification:
1139        // 0x0000   No dropout control is invoked
1140        engine.value_stack.push(0x0000).unwrap();
1141        engine.op_scanctrl().unwrap();
1142        assert!(!engine.graphics.scan_control);
1143        // 0x01FF   Always do dropout control
1144        engine.value_stack.push(0x01FF).unwrap();
1145        engine.op_scanctrl().unwrap();
1146        assert!(engine.graphics.scan_control);
1147        // 0x0A10   Do dropout control if the glyph is rotated and has less than 16 pixels per-em
1148        engine.value_stack.push(0x0A10).unwrap();
1149        engine.graphics.is_rotated = true;
1150        engine.graphics.ppem = 12;
1151        engine.op_scanctrl().unwrap();
1152        assert!(engine.graphics.scan_control);
1153    }
1154}