1 //! Rounding state.
2 
3 use super::GraphicsState;
4 
5 /// Rounding strategies supported by the interpreter.
6 #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
7 pub enum RoundMode {
8     /// Distances are rounded to the closest grid line.
9     ///
10     /// Set by `RTG` instruction.
11     #[default]
12     Grid,
13     /// Distances are rounded to the nearest half grid line.
14     ///
15     /// Set by `RTHG` instruction.
16     HalfGrid,
17     /// Distances are rounded to the closest half or integer pixel.
18     ///
19     /// Set by `RTDG` instruction.
20     DoubleGrid,
21     /// Distances are rounded down to the closest integer grid line.
22     ///
23     /// Set by `RDTG` instruction.
24     DownToGrid,
25     /// Distances are rounded up to the closest integer pixel boundary.
26     ///
27     /// Set by `RUTG` instruction.
28     UpToGrid,
29     /// Rounding is turned off.
30     ///
31     /// Set by `ROFF` instruction.
32     Off,
33     /// Allows fine control over the effects of the round state variable by
34     /// allowing you to set the values of three components of the round_state:
35     /// period, phase, and threshold.
36     ///
37     /// More formally, maps the domain of 26.6 fixed point numbers into a set
38     /// of discrete values that are separated by equal distances.
39     ///
40     /// Set by `SROUND` instruction.
41     Super,
42     /// Analogous to `Super`. The grid period is sqrt(2)/2 pixels rather than 1
43     /// pixel. It is useful for measuring at a 45 degree angle with the
44     /// coordinate axes.
45     ///
46     /// Set by `S45ROUND` instruction.
47     Super45,
48 }
49 
50 /// Graphics state that controls rounding.
51 ///
52 /// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM04/Chap4.html#round%20state>
53 #[derive(Copy, Clone, Debug)]
54 pub struct RoundState {
55     pub mode: RoundMode,
56     pub threshold: i32,
57     pub phase: i32,
58     pub period: i32,
59 }
60 
61 impl Default for RoundState {
default() -> Self62     fn default() -> Self {
63         Self {
64             mode: RoundMode::Grid,
65             threshold: 0,
66             phase: 0,
67             period: 64,
68         }
69     }
70 }
71 
72 impl RoundState {
round(&self, distance: i32) -> i3273     pub fn round(&self, distance: i32) -> i32 {
74         use super::super::math;
75         use RoundMode::*;
76         match self.mode {
77             // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L1958>
78             HalfGrid => {
79                 if distance >= 0 {
80                     (math::floor(distance) + 32).max(0)
81                 } else {
82                     (-(math::floor(-distance) + 32)).min(0)
83                 }
84             }
85             // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L1913>
86             Grid => {
87                 if distance >= 0 {
88                     math::round(distance).max(0)
89                 } else {
90                     (-math::round(-distance)).min(0)
91                 }
92             }
93             // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2094>
94             DoubleGrid => {
95                 if distance >= 0 {
96                     math::round_pad(distance, 32).max(0)
97                 } else {
98                     (-math::round_pad(-distance, 32)).min(0)
99                 }
100             }
101             // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2005>
102             DownToGrid => {
103                 if distance >= 0 {
104                     math::floor(distance).max(0)
105                 } else {
106                     (-math::floor(-distance)).min(0)
107                 }
108             }
109             // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2049>
110             UpToGrid => {
111                 if distance >= 0 {
112                     math::ceil(distance).max(0)
113                 } else {
114                     (-math::ceil(-distance)).min(0)
115                 }
116             }
117             // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2145>
118             Super => {
119                 if distance >= 0 {
120                     let val =
121                         ((distance + (self.threshold - self.phase)) & -self.period) + self.phase;
122                     if val < 0 {
123                         self.phase
124                     } else {
125                         val
126                     }
127                 } else {
128                     let val =
129                         -(((self.threshold - self.phase) - distance) & -self.period) - self.phase;
130                     if val > 0 {
131                         -self.phase
132                     } else {
133                         val
134                     }
135                 }
136             }
137             // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2199>
138             Super45 => {
139                 if distance >= 0 {
140                     let val = (((distance + (self.threshold - self.phase)) / self.period)
141                         * self.period)
142                         + self.phase;
143                     if val < 0 {
144                         self.phase
145                     } else {
146                         val
147                     }
148                 } else {
149                     let val = -((((self.threshold - self.phase) - distance) / self.period)
150                         * self.period)
151                         - self.phase;
152                     if val > 0 {
153                         -self.phase
154                     } else {
155                         val
156                     }
157                 }
158             }
159             // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L1870>
160             Off => distance,
161         }
162     }
163 }
164 
165 impl GraphicsState<'_> {
round(&self, distance: i32) -> i32166     pub fn round(&self, distance: i32) -> i32 {
167         self.round_state.round(distance)
168     }
169 }
170 
171 #[cfg(test)]
172 mod tests {
173     use super::{RoundMode, RoundState};
174 
175     #[test]
round_to_grid()176     fn round_to_grid() {
177         round_cases(
178             RoundMode::Grid,
179             &[(0, 0), (32, 64), (-32, -64), (64, 64), (50, 64)],
180         );
181     }
182 
183     #[test]
round_to_half_grid()184     fn round_to_half_grid() {
185         round_cases(
186             RoundMode::HalfGrid,
187             &[(0, 32), (32, 32), (-32, -32), (64, 96), (50, 32)],
188         );
189     }
190 
191     #[test]
round_to_double_grid()192     fn round_to_double_grid() {
193         round_cases(
194             RoundMode::DoubleGrid,
195             &[(0, 0), (32, 32), (-32, -32), (64, 64), (50, 64)],
196         );
197     }
198 
199     #[test]
round_down_to_grid()200     fn round_down_to_grid() {
201         round_cases(
202             RoundMode::DownToGrid,
203             &[(0, 0), (32, 0), (-32, 0), (64, 64), (50, 0)],
204         );
205     }
206 
207     #[test]
round_up_to_grid()208     fn round_up_to_grid() {
209         round_cases(
210             RoundMode::UpToGrid,
211             &[(0, 0), (32, 64), (-32, -64), (64, 64), (50, 64)],
212         );
213     }
214 
215     #[test]
round_off()216     fn round_off() {
217         round_cases(
218             RoundMode::Off,
219             &[(0, 0), (32, 32), (-32, -32), (64, 64), (50, 50)],
220         );
221     }
222 
round_cases(mode: RoundMode, cases: &[(i32, i32)])223     fn round_cases(mode: RoundMode, cases: &[(i32, i32)]) {
224         for (value, expected) in cases.iter().copied() {
225             let state = RoundState {
226                 mode,
227                 ..Default::default()
228             };
229             let result = state.round(value);
230             assert_eq!(
231                 result, expected,
232                 "mismatch in rounding: {mode:?}({value}) = {result} (expected {expected})"
233             );
234         }
235     }
236 }
237