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}