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}