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}