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