skrifa/outline/glyf/hint/engine/
logical.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
//! Logical functions.
//!
//! Implements 11 instructions.
//!
//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#logical-functions>

use super::{Engine, F26Dot6, OpResult};

impl<'a> Engine<'a> {
    /// Less than.
    ///
    /// LT[] (0x50)
    ///
    /// Pops: e1, e2
    /// Pushes: Boolean value
    ///
    /// First pops e2, then pops e1 off the stack and compares them: if e1 is
    /// less than e2, 1, signifying TRUE, is pushed onto the stack. If e1 is
    /// not less than e2, 0, signifying FALSE, is placed onto the stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#less-than>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2721>
    pub(super) fn op_lt(&mut self) -> OpResult {
        self.value_stack.apply_binary(|a, b| Ok((a < b) as i32))
    }

    /// Less than or equal.
    ///
    /// LTEQ[] (0x51)
    ///
    /// Pops: e1, e2
    /// Pushes: Boolean value
    ///
    /// Pops e2 and e1 off the stack and compares them. If e1 is less than or
    /// equal to e2, 1, signifying TRUE, is pushed onto the stack. If e1 is
    /// not less than or equal to e2, 0, signifying FALSE, is placed onto the
    /// stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#less-than-or-equal>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2734>
    pub(super) fn op_lteq(&mut self) -> OpResult {
        self.value_stack.apply_binary(|a, b| Ok((a <= b) as i32))
    }

    /// Greater than.
    ///
    /// GT[] (0x52)
    ///
    /// Pops: e1, e2
    /// Pushes: Boolean value
    ///
    /// First pops e2 then pops e1 off the stack and compares them. If e1 is
    /// greater than e2, 1, signifying TRUE, is pushed onto the stack. If e1
    /// is not greater than e2, 0, signifying FALSE, is placed onto the stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#greater-than>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2747>
    pub(super) fn op_gt(&mut self) -> OpResult {
        self.value_stack.apply_binary(|a, b| Ok((a > b) as i32))
    }

    /// Greater than or equal.
    ///
    /// GTEQ[] (0x53)
    ///
    /// Pops: e1, e2
    /// Pushes: Boolean value
    ///
    /// Pops e1 and e2 off the stack and compares them. If e1 is greater than
    /// or equal to e2, 1, signifying TRUE, is pushed onto the stack. If e1
    /// is not greater than or equal to e2, 0, signifying FALSE, is placed
    /// onto the stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#greater-than-or-equal>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2760>
    pub(super) fn op_gteq(&mut self) -> OpResult {
        self.value_stack.apply_binary(|a, b| Ok((a >= b) as i32))
    }

    /// Equal.
    ///
    /// EQ[] (0x54)
    ///
    /// Pops: e1, e2
    /// Pushes: Boolean value
    ///
    /// Pops e1 and e2 off the stack and compares them. If they are equal, 1,
    /// signifying TRUE is pushed onto the stack. If they are not equal, 0,
    /// signifying FALSE is placed onto the stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#equal>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2773>
    pub(super) fn op_eq(&mut self) -> OpResult {
        self.value_stack.apply_binary(|a, b| Ok((a == b) as i32))
    }

    /// Not equal.
    ///
    /// NEQ[] (0x55)
    ///
    /// Pops: e1, e2
    /// Pushes: Boolean value
    ///
    /// Pops e1 and e2 from the stack and compares them. If they are not equal,
    /// 1, signifying TRUE, is pushed onto the stack. If they are equal, 0,
    /// signifying FALSE, is placed on the stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#not-equal>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2786>
    pub(super) fn op_neq(&mut self) -> OpResult {
        self.value_stack.apply_binary(|a, b| Ok((a != b) as i32))
    }

    /// Odd.
    ///
    /// ODD[] (0x56)
    ///
    /// Pops: e1
    /// Pushes: Boolean value
    ///
    /// Tests whether the number at the top of the stack is odd. Pops e1 from
    /// the stack and rounds it as specified by the round_state before testing
    /// it. After the value is rounded, it is shifted from a fixed point value
    /// to an integer value (any fractional values are ignored). If the integer
    /// value is odd, one, signifying TRUE, is pushed onto the stack. If it is
    /// even, zero, signifying FALSE is placed onto the stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#odd>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2799>
    pub(super) fn op_odd(&mut self) -> OpResult {
        let round_state = self.graphics.round_state;
        self.value_stack.apply_unary(|e1| {
            Ok((round_state.round(F26Dot6::from_bits(e1)).to_bits() & 127 == 64) as i32)
        })
    }

    /// Even.
    ///
    /// EVEN[] (0x57)
    ///
    /// Pops: e1
    /// Pushes: Boolean value
    ///
    /// Tests whether the number at the top of the stack is even. Pops e1 off
    /// the stack and rounds it as specified by the round_state before testing
    /// it. If the rounded number is even, one, signifying TRUE, is pushed onto
    /// the stack if it is odd, zero, signifying FALSE, is placed onto the
    /// stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#even>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2813>
    pub(super) fn op_even(&mut self) -> OpResult {
        let round_state = self.graphics.round_state;
        self.value_stack.apply_unary(|e1| {
            Ok((round_state.round(F26Dot6::from_bits(e1)).to_bits() & 127 == 0) as i32)
        })
    }

    /// Logical and.
    ///
    /// AND[] (0x5A)
    ///
    /// Pops: e1, e2
    /// Pushes: Boolean value
    ///
    /// Pops e1 and e2 off the stack and pushes onto the stack the result of a
    /// logical and of the two elements. Zero is returned if either or both of
    /// the elements are FALSE (have the value zero). One is returned if both
    /// elements are TRUE (have a non zero value).
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#logical-and>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2827>
    pub(super) fn op_and(&mut self) -> OpResult {
        self.value_stack
            .apply_binary(|a, b| Ok((a != 0 && b != 0) as i32))
    }

    /// Logical or.
    ///
    /// OR[] (0x5B)
    ///
    /// Pops: e1, e2
    /// Pushes: Boolean value
    ///
    /// Pops e1 and e2 off the stack and pushes onto the stack the result of a
    /// logical or operation between the two elements. Zero is returned if both
    /// of the elements are FALSE. One is returned if either one or both of the
    /// elements are TRUE (has a nonzero value).
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#logical-or>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2840>
    pub(super) fn op_or(&mut self) -> OpResult {
        self.value_stack
            .apply_binary(|a, b| Ok((a != 0 || b != 0) as i32))
    }

    /// Logical not.
    ///
    /// NOT[] (0x5C)
    ///
    /// Pops: e
    /// Pushes: (not e): logical negation of e
    ///
    /// Pops e off the stack and returns the result of a logical NOT operation
    /// performed on e. If originally zero, one is pushed onto the stack if
    /// originally nonzero, zero is pushed onto the stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#logical-not>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2853>
    pub(super) fn op_not(&mut self) -> OpResult {
        self.value_stack.apply_unary(|e| Ok((e == 0) as i32))
    }
}

#[cfg(test)]
mod tests {
    use super::super::MockEngine;

    #[test]
    fn compare_ops() {
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        for a in -10..=10 {
            for b in -10..=10 {
                let input = &[a, b];
                engine.test_exec(input, a < b, |engine| {
                    engine.op_lt().unwrap();
                });
                engine.test_exec(input, a <= b, |engine| {
                    engine.op_lteq().unwrap();
                });
                engine.test_exec(input, a > b, |engine| {
                    engine.op_gt().unwrap();
                });
                engine.test_exec(input, a >= b, |engine| {
                    engine.op_gteq().unwrap();
                });
                engine.test_exec(input, a == b, |engine| {
                    engine.op_eq().unwrap();
                });
                engine.test_exec(input, a != b, |engine| {
                    engine.op_neq().unwrap();
                });
            }
        }
    }

    #[test]
    fn parity_ops() {
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        // These operate on 26.6 so values are multiple of 64
        let cases = [
            // (input, is_even)
            (0, true),
            (64, false),
            (128, true),
            (192, false),
            (256, true),
            (57, false),
            (-128, true),
        ];
        for (input, is_even) in cases {
            engine.test_exec(&[input], is_even, |engine| {
                engine.op_even().unwrap();
            });
        }
        for (input, is_even) in cases {
            engine.test_exec(&[input], !is_even, |engine| {
                engine.op_odd().unwrap();
            });
        }
    }

    #[test]
    fn not_op() {
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        engine.test_exec(&[0], 1, |engine| {
            engine.op_not().unwrap();
        });
        engine.test_exec(&[234234], 0, |engine| {
            engine.op_not().unwrap();
        });
    }

    #[test]
    fn and_or_ops() {
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        for a in -10..=10 {
            for b in -10..=10 {
                let input = &[a, b];
                let a = a != 0;
                let b = b != 0;
                engine.test_exec(input, a && b, |engine| {
                    engine.op_and().unwrap();
                });
                engine.test_exec(input, a || b, |engine| {
                    engine.op_or().unwrap();
                });
            }
        }
    }
}