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/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2799>
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() & 127 == 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/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2813>
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() & 127 == 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 super::super::MockEngine;
218
219 #[test]
220 fn compare_ops() {
221 let mut mock = MockEngine::new();
222 let mut engine = mock.engine();
223 for a in -10..=10 {
224 for b in -10..=10 {
225 let input = &[a, b];
226 engine.test_exec(input, a < b, |engine| {
227 engine.op_lt().unwrap();
228 });
229 engine.test_exec(input, a <= b, |engine| {
230 engine.op_lteq().unwrap();
231 });
232 engine.test_exec(input, a > b, |engine| {
233 engine.op_gt().unwrap();
234 });
235 engine.test_exec(input, a >= b, |engine| {
236 engine.op_gteq().unwrap();
237 });
238 engine.test_exec(input, a == b, |engine| {
239 engine.op_eq().unwrap();
240 });
241 engine.test_exec(input, a != b, |engine| {
242 engine.op_neq().unwrap();
243 });
244 }
245 }
246 }
247
248 #[test]
249 fn parity_ops() {
250 let mut mock = MockEngine::new();
251 let mut engine = mock.engine();
252 // These operate on 26.6 so values are multiple of 64
253 let cases = [
254 // (input, is_even)
255 (0, true),
256 (64, false),
257 (128, true),
258 (192, false),
259 (256, true),
260 (57, false),
261 (-128, true),
262 ];
263 for (input, is_even) in cases {
264 engine.test_exec(&[input], is_even, |engine| {
265 engine.op_even().unwrap();
266 });
267 }
268 for (input, is_even) in cases {
269 engine.test_exec(&[input], !is_even, |engine| {
270 engine.op_odd().unwrap();
271 });
272 }
273 }
274
275 #[test]
276 fn not_op() {
277 let mut mock = MockEngine::new();
278 let mut engine = mock.engine();
279 engine.test_exec(&[0], 1, |engine| {
280 engine.op_not().unwrap();
281 });
282 engine.test_exec(&[234234], 0, |engine| {
283 engine.op_not().unwrap();
284 });
285 }
286
287 #[test]
288 fn and_or_ops() {
289 let mut mock = MockEngine::new();
290 let mut engine = mock.engine();
291 for a in -10..=10 {
292 for b in -10..=10 {
293 let input = &[a, b];
294 let a = a != 0;
295 let b = b != 0;
296 engine.test_exec(input, a && b, |engine| {
297 engine.op_and().unwrap();
298 });
299 engine.test_exec(input, a || b, |engine| {
300 engine.op_or().unwrap();
301 });
302 }
303 }
304 }
305}