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