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