skrifa/outline/glyf/hint/engine/logical.rs
1//! Logical functions.
2//!
3//! Implements 11 instructions.
4//!
5//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#logical-functions>
6
7use super::{Engine, F26Dot6, OpResult};
8
9impl Engine<'_> {
10 /// Less than.
11 ///
12 /// LT[] (0x50)
13 ///
14 /// Pops: e1, e2
15 /// Pushes: Boolean value
16 ///
17 /// First pops e2, then pops e1 off the stack and compares them: if e1 is
18 /// less than e2, 1, signifying TRUE, is pushed onto the stack. If e1 is
19 /// not less than e2, 0, signifying FALSE, is placed onto the stack.
20 ///
21 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#less-than>
22 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2721>
23 pub(super) fn op_lt(&mut self) -> OpResult {
24 self.value_stack.apply_binary(|a, b| Ok((a < b) as i32))
25 }
26
27 /// Less than or equal.
28 ///
29 /// LTEQ[] (0x51)
30 ///
31 /// Pops: e1, e2
32 /// Pushes: Boolean value
33 ///
34 /// Pops e2 and e1 off the stack and compares them. If e1 is less than or
35 /// equal to e2, 1, signifying TRUE, is pushed onto the stack. If e1 is
36 /// not less than or equal to e2, 0, signifying FALSE, is placed onto the
37 /// stack.
38 ///
39 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#less-than-or-equal>
40 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2734>
41 pub(super) fn op_lteq(&mut self) -> OpResult {
42 self.value_stack.apply_binary(|a, b| Ok((a <= b) as i32))
43 }
44
45 /// Greater than.
46 ///
47 /// GT[] (0x52)
48 ///
49 /// Pops: e1, e2
50 /// Pushes: Boolean value
51 ///
52 /// First pops e2 then pops e1 off the stack and compares them. If e1 is
53 /// greater than e2, 1, signifying TRUE, is pushed onto the stack. If e1
54 /// is not greater than e2, 0, signifying FALSE, is placed onto the stack.
55 ///
56 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#greater-than>
57 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2747>
58 pub(super) fn op_gt(&mut self) -> OpResult {
59 self.value_stack.apply_binary(|a, b| Ok((a > b) as i32))
60 }
61
62 /// Greater than or equal.
63 ///
64 /// GTEQ[] (0x53)
65 ///
66 /// Pops: e1, e2
67 /// Pushes: Boolean value
68 ///
69 /// Pops e1 and e2 off the stack and compares them. If e1 is greater than
70 /// or equal to e2, 1, signifying TRUE, is pushed onto the stack. If e1
71 /// is not greater than or equal to e2, 0, signifying FALSE, is placed
72 /// onto the stack.
73 ///
74 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#greater-than-or-equal>
75 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2760>
76 pub(super) fn op_gteq(&mut self) -> OpResult {
77 self.value_stack.apply_binary(|a, b| Ok((a >= b) as i32))
78 }
79
80 /// Equal.
81 ///
82 /// EQ[] (0x54)
83 ///
84 /// Pops: e1, e2
85 /// Pushes: Boolean value
86 ///
87 /// Pops e1 and e2 off the stack and compares them. If they are equal, 1,
88 /// signifying TRUE is pushed onto the stack. If they are not equal, 0,
89 /// signifying FALSE is placed onto the stack.
90 ///
91 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#equal>
92 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2773>
93 pub(super) fn op_eq(&mut self) -> OpResult {
94 self.value_stack.apply_binary(|a, b| Ok((a == b) as i32))
95 }
96
97 /// Not equal.
98 ///
99 /// NEQ[] (0x55)
100 ///
101 /// Pops: e1, e2
102 /// Pushes: Boolean value
103 ///
104 /// Pops e1 and e2 from the stack and compares them. If they are not equal,
105 /// 1, signifying TRUE, is pushed onto the stack. If they are equal, 0,
106 /// signifying FALSE, is placed on the stack.
107 ///
108 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#not-equal>
109 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2786>
110 pub(super) fn op_neq(&mut self) -> OpResult {
111 self.value_stack.apply_binary(|a, b| Ok((a != b) as i32))
112 }
113
114 /// Odd.
115 ///
116 /// ODD[] (0x56)
117 ///
118 /// Pops: e1
119 /// Pushes: Boolean value
120 ///
121 /// Tests whether the number at the top of the stack is odd. Pops e1 from
122 /// the stack and rounds it as specified by the round_state before testing
123 /// it. After the value is rounded, it is shifted from a fixed point value
124 /// to an integer value (any fractional values are ignored). If the integer
125 /// value is odd, one, signifying TRUE, is pushed onto the stack. If it is
126 /// even, zero, signifying FALSE is placed onto the stack.
127 ///
128 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#odd>
129 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/47d80cf27b05d1062fbe5d3d368c1bac7ff1a20c/src/truetype/ttinterp.c#L2567>
130 pub(super) fn op_odd(&mut self) -> OpResult {
131 let round_state = self.graphics.round_state;
132 self.value_stack.apply_unary(|e1| {
133 Ok((round_state.round(F26Dot6::from_bits(e1)).to_bits() & 64 == 64) as i32)
134 })
135 }
136
137 /// Even.
138 ///
139 /// EVEN[] (0x57)
140 ///
141 /// Pops: e1
142 /// Pushes: Boolean value
143 ///
144 /// Tests whether the number at the top of the stack is even. Pops e1 off
145 /// the stack and rounds it as specified by the round_state before testing
146 /// it. If the rounded number is even, one, signifying TRUE, is pushed onto
147 /// the stack if it is odd, zero, signifying FALSE, is placed onto the
148 /// stack.
149 ///
150 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#even>
151 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/47d80cf27b05d1062fbe5d3d368c1bac7ff1a20c/src/truetype/ttinterp.c#L2581>
152 pub(super) fn op_even(&mut self) -> OpResult {
153 let round_state = self.graphics.round_state;
154 self.value_stack.apply_unary(|e1| {
155 Ok((round_state.round(F26Dot6::from_bits(e1)).to_bits() & 64 == 0) as i32)
156 })
157 }
158
159 /// Logical and.
160 ///
161 /// AND[] (0x5A)
162 ///
163 /// Pops: e1, e2
164 /// Pushes: Boolean value
165 ///
166 /// Pops e1 and e2 off the stack and pushes onto the stack the result of a
167 /// logical and of the two elements. Zero is returned if either or both of
168 /// the elements are FALSE (have the value zero). One is returned if both
169 /// elements are TRUE (have a non zero value).
170 ///
171 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#logical-and>
172 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2827>
173 pub(super) fn op_and(&mut self) -> OpResult {
174 self.value_stack
175 .apply_binary(|a, b| Ok((a != 0 && b != 0) as i32))
176 }
177
178 /// Logical or.
179 ///
180 /// OR[] (0x5B)
181 ///
182 /// Pops: e1, e2
183 /// Pushes: Boolean value
184 ///
185 /// Pops e1 and e2 off the stack and pushes onto the stack the result of a
186 /// logical or operation between the two elements. Zero is returned if both
187 /// of the elements are FALSE. One is returned if either one or both of the
188 /// elements are TRUE (has a nonzero value).
189 ///
190 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#logical-or>
191 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2840>
192 pub(super) fn op_or(&mut self) -> OpResult {
193 self.value_stack
194 .apply_binary(|a, b| Ok((a != 0 || b != 0) as i32))
195 }
196
197 /// Logical not.
198 ///
199 /// NOT[] (0x5C)
200 ///
201 /// Pops: e
202 /// Pushes: (not e): logical negation of e
203 ///
204 /// Pops e off the stack and returns the result of a logical NOT operation
205 /// performed on e. If originally zero, one is pushed onto the stack if
206 /// originally nonzero, zero is pushed onto the stack.
207 ///
208 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#logical-not>
209 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2853>
210 pub(super) fn op_not(&mut self) -> OpResult {
211 self.value_stack.apply_unary(|e| Ok((e == 0) as i32))
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use crate::outline::glyf::hint::round::RoundMode;
218
219 use super::super::MockEngine;
220
221 #[test]
222 fn compare_ops() {
223 let mut mock = MockEngine::new();
224 let mut engine = mock.engine();
225 for a in -10..=10 {
226 for b in -10..=10 {
227 let input = &[a, b];
228 engine.test_exec(input, a < b, |engine| {
229 engine.op_lt().unwrap();
230 });
231 engine.test_exec(input, a <= b, |engine| {
232 engine.op_lteq().unwrap();
233 });
234 engine.test_exec(input, a > b, |engine| {
235 engine.op_gt().unwrap();
236 });
237 engine.test_exec(input, a >= b, |engine| {
238 engine.op_gteq().unwrap();
239 });
240 engine.test_exec(input, a == b, |engine| {
241 engine.op_eq().unwrap();
242 });
243 engine.test_exec(input, a != b, |engine| {
244 engine.op_neq().unwrap();
245 });
246 }
247 }
248 }
249
250 #[test]
251 fn parity_ops() {
252 let mut mock = MockEngine::new();
253 let mut engine = mock.engine();
254 // These operate on 26.6 so values are multiple of 64
255 let cases = [
256 // (input, is_even)
257 (0, true),
258 (64, false),
259 (128, true),
260 (192, false),
261 (256, true),
262 (57, false),
263 (-128, true),
264 ];
265 for (input, is_even) in cases {
266 engine.test_exec(&[input], is_even, |engine| {
267 engine.op_even().unwrap();
268 });
269 }
270 for (input, is_even) in cases {
271 engine.test_exec(&[input], !is_even, |engine| {
272 engine.op_odd().unwrap();
273 });
274 }
275 }
276
277 #[test]
278 fn parity_fractional_ops() {
279 let mut mock = MockEngine::new();
280 let mut engine = mock.engine();
281 engine.graphics.round_state.mode = RoundMode::HalfGrid;
282 // In half grid rounding, 0 rounds to 0.5
283 engine.test_exec(&[0], 32, |engine| {
284 engine.op_round().unwrap();
285 });
286 // Ensure we drop the fractional part before checking parity.
287 engine.test_exec(&[0], true, |engine| {
288 engine.op_even().unwrap();
289 });
290 engine.test_exec(&[0], false, |engine| {
291 engine.op_odd().unwrap();
292 });
293 }
294
295 #[test]
296 fn not_op() {
297 let mut mock = MockEngine::new();
298 let mut engine = mock.engine();
299 engine.test_exec(&[0], 1, |engine| {
300 engine.op_not().unwrap();
301 });
302 engine.test_exec(&[234234], 0, |engine| {
303 engine.op_not().unwrap();
304 });
305 }
306
307 #[test]
308 fn and_or_ops() {
309 let mut mock = MockEngine::new();
310 let mut engine = mock.engine();
311 for a in -10..=10 {
312 for b in -10..=10 {
313 let input = &[a, b];
314 let a = a != 0;
315 let b = b != 0;
316 engine.test_exec(input, a && b, |engine| {
317 engine.op_and().unwrap();
318 });
319 engine.test_exec(input, a || b, |engine| {
320 engine.op_or().unwrap();
321 });
322 }
323 }
324 }
325}