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

1//! Arithmetic and math instructions.
2//!
3//! Implements 10 instructions.
4//!
5//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#arithmetic-and-math-instructions>
6
7use super::{super::math, Engine, HintErrorKind, OpResult};
8
9impl Engine<'_> {
10    /// ADD[] (0x60)
11    ///
12    /// Pops: n1, n2 (F26Dot6)
13    /// Pushes: (n2 + n1)
14    ///
15    /// Pops n1 and n2 off the stack and pushes the sum of the two elements
16    /// onto the stack.
17    ///
18    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#add>
19    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2866>
20    pub(super) fn op_add(&mut self) -> OpResult {
21        self.value_stack.apply_binary(|a, b| Ok(a.wrapping_add(b)))
22    }
23
24    /// SUB[] (0x61)
25    ///
26    /// Pops: n1, n2 (F26Dot6)
27    /// Pushes: (n2 - n1)
28    ///
29    /// Pops n1 and n2 off the stack and pushes the difference of the two
30    /// elements onto the stack.
31    ///
32    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#subtract>
33    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2879>
34    pub(super) fn op_sub(&mut self) -> OpResult {
35        self.value_stack.apply_binary(|a, b| Ok(a.wrapping_sub(b)))
36    }
37
38    /// DIV[] (0x62)
39    ///
40    /// Pops: n1, n2 (F26Dot6)
41    /// Pushes: (n2 / n1)
42    ///
43    /// Pops n1 and n2 off the stack and pushes onto the stack the quotient
44    /// obtained by dividing n2 by n1. Note that this truncates rather than
45    /// rounds the value.
46    ///
47    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#divide>
48    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2892>
49    pub(super) fn op_div(&mut self) -> OpResult {
50        self.value_stack.apply_binary(|a, b| {
51            if b == 0 {
52                Err(HintErrorKind::DivideByZero)
53            } else {
54                Ok(math::mul_div_no_round(a, 64, b))
55            }
56        })
57    }
58
59    /// MUL[] (0x63)
60    ///
61    /// Pops: n1, n2 (F26Dot6)
62    /// Pushes: (n2 * n1)
63    ///
64    /// Pops n1 and n2 off the stack and pushes onto the stack the product of
65    /// the two elements.
66    ///
67    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#multiply>
68    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2909>
69    pub(super) fn op_mul(&mut self) -> OpResult {
70        self.value_stack
71            .apply_binary(|a, b| Ok(math::mul_div(a, b, 64)))
72    }
73
74    /// ABS[] (0x64)
75    ///
76    /// Pops: n
77    /// Pushes: |n|: absolute value of n (F26Dot6)
78    ///
79    /// Pops n off the stack and pushes onto the stack the absolute value of n.
80    ///
81    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#absolute-value>
82    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2922>
83    pub(super) fn op_abs(&mut self) -> OpResult {
84        self.value_stack.apply_unary(|n| Ok(n.wrapping_abs()))
85    }
86
87    /// NEG[] (0x65)
88    ///
89    /// Pops: n1
90    /// Pushes: -n1: negation of n1 (F26Dot6)
91    ///
92    /// This instruction pops n1 off the stack and pushes onto the stack the
93    /// negated value of n1.
94    ///
95    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#negate>
96    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2936>
97    pub(super) fn op_neg(&mut self) -> OpResult {
98        self.value_stack.apply_unary(|n1| Ok(n1.wrapping_neg()))
99    }
100
101    /// FLOOR[] (0x66)
102    ///
103    /// Pops: n1: number whose floor is desired (F26Dot6)
104    /// Pushes: n: floor of n1 (F26Dot6)
105    ///
106    /// Pops n1 and returns n, the greatest integer value less than or equal to n1.
107    ///
108    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#floor>
109    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2949>
110    pub(super) fn op_floor(&mut self) -> OpResult {
111        self.value_stack.apply_unary(|n1| Ok(math::floor(n1)))
112    }
113
114    /// CEILING[] (0x67)
115    ///
116    /// Pops: n1: number whose ceiling is desired (F26Dot6)
117    /// Pushes: n: ceiling of n1 (F26Dot6)
118    ///
119    /// Pops n1 and returns n, the least integer value greater than or equal to n1.
120    ///
121    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#ceiling>
122    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2962>
123    pub(super) fn op_ceiling(&mut self) -> OpResult {
124        self.value_stack.apply_unary(|n1| Ok(math::ceil(n1)))
125    }
126
127    /// MAX[] (0x8B)
128    ///
129    /// Pops: e1, e2
130    /// Pushes: maximum of e1 and e2
131    ///
132    /// Pops two elements, e1 and e2, from the stack and pushes the larger of
133    /// these two quantities onto the stack.
134    ///
135    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#maximum-of-top-two-stack-elements>
136    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3171>
137    pub(super) fn op_max(&mut self) -> OpResult {
138        self.value_stack.apply_binary(|a, b| Ok(a.max(b)))
139    }
140
141    /// MIN[] (0x8C)
142    ///
143    /// Pops: e1, e2
144    /// Pushes: minimum of e1 and e2
145    ///
146    /// Pops two elements, e1 and e2, from the stack and pushes the smaller
147    /// of these two quantities onto the stack.
148    ///
149    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#minimum-of-top-two-stack-elements>
150    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3185>
151    pub(super) fn op_min(&mut self) -> OpResult {
152        self.value_stack.apply_binary(|a, b| Ok(a.min(b)))
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::{super::MockEngine, math, HintErrorKind};
159
160    /// Test the binary operations that don't require fixed point
161    /// arithmetic.
162    #[test]
163    fn simple_binops() {
164        let mut mock = MockEngine::new();
165        let mut engine = mock.engine();
166        for a in -10..=10 {
167            for b in -10..=10 {
168                let input = &[a, b];
169                engine.test_exec(input, a + b, |engine| {
170                    engine.op_add().unwrap();
171                });
172                engine.test_exec(input, a - b, |engine| {
173                    engine.op_sub().unwrap();
174                });
175                engine.test_exec(input, a.max(b), |engine| {
176                    engine.op_max().unwrap();
177                });
178                engine.test_exec(input, a.min(b), |engine| {
179                    engine.op_min().unwrap();
180                });
181            }
182        }
183    }
184
185    /// Test the unary operations that don't require fixed point
186    /// arithmetic.
187    #[test]
188    fn simple_unops() {
189        let mut mock = MockEngine::new();
190        let mut engine = mock.engine();
191        for a in -10..=10 {
192            let input = &[a];
193            engine.test_exec(input, -a, |engine| {
194                engine.op_neg().unwrap();
195            });
196            engine.test_exec(input, a.abs(), |engine| {
197                engine.op_abs().unwrap();
198            });
199        }
200    }
201
202    #[test]
203    fn f26dot6_binops() {
204        let mut mock = MockEngine::new();
205        let mut engine = mock.engine();
206        for a in -10..=10 {
207            for b in -10..=10 {
208                let a = a * 64 + 30;
209                let b = b * 64 - 30;
210                let input = &[a, b];
211                engine.test_exec(input, math::mul_div(a, b, 64), |engine| {
212                    engine.op_mul().unwrap();
213                });
214                if b != 0 {
215                    engine.test_exec(input, math::mul_div_no_round(a, 64, b), |engine| {
216                        engine.op_div().unwrap();
217                    });
218                } else {
219                    engine.value_stack.push(a).unwrap();
220                    engine.value_stack.push(b).unwrap();
221                    assert!(matches!(engine.op_div(), Err(HintErrorKind::DivideByZero)));
222                }
223            }
224        }
225    }
226
227    #[test]
228    fn f26dot6_unops() {
229        let mut mock = MockEngine::new();
230        let mut engine = mock.engine();
231        for a in -10..=10 {
232            for b in -10..=10 {
233                let a = a * 64 + b;
234                let input = &[a];
235                engine.test_exec(input, math::floor(a), |engine| {
236                    engine.op_floor().unwrap();
237                });
238                engine.test_exec(input, math::ceil(a), |engine| {
239                    engine.op_ceiling().unwrap();
240                });
241            }
242        }
243    }
244}