ttf_view/types/affine2x2.rs
1use crate::types::F2Dot14;
2
3/// A simple [`F2Dot14`] 2×2 affine transformation
4/// <span class="hidden">`[xx, yx; xy, yy]`</span>
5/// <math><mo>[</mo><mtable>
6/// <mtr><mtd><mi>xx</mi></mtd><mtd><mi>yx</mi></mtd></mtr>
7/// <mtr><mtd><mi>xy</mi></mtd><mtd><mi>yy</mi></mtd></mtr>
8/// </mtable><mo>]</mo></math>.
9#[derive(Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Affine2x2 {
11 /// X-component of transformed X-basis vector.
12 pub xx: F2Dot14,
13 /// Y-component of transformed X-basis vector (aka. `scale01` in `glyf`).
14 pub yx: F2Dot14, // aka. scale01
15 /// X-component of transformed Y-basis vector (aka. `scale10` in `glyf`).
16 pub xy: F2Dot14, // aka. scale10
17 /// Y-component of transformed Y-basis vector.
18 pub yy: F2Dot14,
19}
20
21const impl Default for Affine2x2 {
22 fn default() -> Self {
23 Self::IDENTITY
24 }
25}
26
27impl Affine2x2 {
28 /// The identity transform
29 /// <span class="hidden">`[1, 0; 0, 1]`</span>
30 /// <math><mo>[</mo><mtable>
31 /// <mtr><mtd><mn>1</mn></mtd><mtd><mn>0</mn></mtd></mtr>
32 /// <mtr><mtd><mn>0</mn></mtd><mtd><mn>1</mn></mtd></mtr>
33 /// </mtable><mo>]</mo></math>.
34 ///
35 /// # Examples
36 ///
37 /// ```
38 /// # use ttf_view::types::Affine2x2;
39 /// assert_eq!(Affine2x2::IDENTITY.to_tuple_f32(), (1.0, 0.0, 0.0, 1.0));
40 /// ```
41 pub const IDENTITY: Self = Self::scale(F2Dot14::ONE);
42
43 /// Creates an [`Affine2x2`] with
44 /// <span class="hidden">`[xx, yx; xy, yy]`</span>
45 /// <math><mo>[</mo><mtable>
46 /// <mtr><mtd><mi>xx</mi></mtd><mtd><mi>yx</mi></mtd></mtr>
47 /// <mtr><mtd><mi>xy</mi></mtd><mtd><mi>yy</mi></mtd></mtr>
48 /// </mtable><mo>]</mo></math>.
49 pub const fn new(xx: F2Dot14, yx: F2Dot14, xy: F2Dot14, yy: F2Dot14) -> Self {
50 Self { xx, yx, xy, yy }
51 }
52
53 /// Creates an [`Affine2x2`] with
54 /// <span class="hidden">`[scale, 0; 0, scale]`</span>
55 /// <math><mo>[</mo><mtable>
56 /// <mtr><mtd><mi>scale</mi></mtd><mtd><mn>0</mn></mtd></mtr>
57 /// <mtr><mtd><mn>0</mn></mtd><mtd><mi>scale</mi></mtd></mtr>
58 /// </mtable><mo>]</mo></math>.
59 pub const fn scale(scale: F2Dot14) -> Self {
60 Self::new(scale, F2Dot14::ZERO, F2Dot14::ZERO, scale)
61 }
62 /// Creates an [`Affine2x2`] with
63 /// <span class="hidden">`[x, 0; 0, y]`</span>
64 /// <math><mo>[</mo><mtable>
65 /// <mtr><mtd><mi>x</mi></mtd><mtd><mn>0</mn></mtd></mtr>
66 /// <mtr><mtd><mn>0</mn></mtd><mtd><mi>y</mi></mtd></mtr>
67 /// </mtable><mo>]</mo></math>.
68 pub const fn scale_xy(x: F2Dot14, y: F2Dot14) -> Self {
69 Self::new(x, F2Dot14::ZERO, F2Dot14::ZERO, y)
70 }
71
72 /// Creates an [`Affine2x2`] with
73 /// <span class="hidden">`[cos(θ),-sin(θ);sin(θ),cos(θ)]`</span>
74 /// <math><mo>[</mo><mtable>
75 /// <mtr><mtd><mi>cos(θ)</mi></mtd><mtd><mi>-sin(θ)</mi></mtd></mtr>
76 /// <mtr><mtd><mi>sin(θ)</mi></mtd><mtd><mi>cos(θ)</mi></mtd></mtr>
77 /// </mtable><mo>]</mo></math>,
78 /// rotating the image counter-clockwise by specified angle.
79 pub fn rotation(radians: f32) -> Self {
80 let (sin, cos) = radians.sin_cos();
81 Self::new(
82 F2Dot14::new(cos).unwrap(),
83 F2Dot14::new(-sin).unwrap(),
84 F2Dot14::new(sin).unwrap(),
85 F2Dot14::new(cos).unwrap(),
86 )
87 }
88 /// Creates an [`Affine2x2`] with
89 /// <span class="hidden">`[cos(θ),-sin(θ); sin(θ),cos(θ)]`</span>
90 /// <math><mo>[</mo><mtable>
91 /// <mtr><mtd><mi>cos(θ)</mi></mtd><mtd><mi>-sin(θ)</mi></mtd></mtr>
92 /// <mtr><mtd><mi>sin(θ)</mi></mtd><mtd><mi>cos(θ)</mi></mtd></mtr>
93 /// </mtable><mo>]</mo></math>,
94 /// rotating the image counter-clockwise by specified angle.
95 pub fn rotation_degrees(degrees: f32) -> Self {
96 Self::rotation(degrees.to_radians())
97 }
98
99 /// Applies this [`Affine2x2`] transformation to the point
100 /// <span class="hidden">`[x, y]`</span>
101 /// <math><mo>\[</mo><mi>x</mi><mo> </mo><mi>y</mi><mo>\]</mo></math>:
102 ///
103 /// <code class="hidden">\[x,y\]×\[xx,yx; xy,yy\]=\[xx\*x+xy\*y, yx\*x+yy\*y\]</code>
104 /// <math>
105 /// <mrow><mo>\[</mo><mi>x</mi><mo> </mo><mi>y</mi><mo>\]</mo></mrow>
106 /// <mo>×</mo>
107 /// <mo>\[</mo><mtable>
108 /// <mtr><mtd><mi>xx</mi></mtd><mtd><mi>yx</mi></mtd></mtr>
109 /// <mtr><mtd><mi>xy</mi></mtd><mtd><mi>yy</mi></mtd></mtr>
110 /// </mtable><mo>\]</mo>
111 /// <mo>=</mo>
112 /// <mrow><mo>\[</mo>
113 /// <mi>xx</mi><mo>\*</mo><mi>x</mi><mo>+</mo><mi>xy</mi><mo>\*</mo><mi>y</mi>
114 /// <mo>, </mo>
115 /// <mi>yx</mi><mo>\*</mo><mi>x</mi><mo>+</mo><mi>yy</mi><mo>\*</mo><mi>y</mi>
116 /// <mo>\]</mo></mrow>
117 /// </math>
118 ///
119 /// # Examples
120 ///
121 /// ```
122 /// use ttf_view::types::Affine2x2;
123 ///
124 /// assert_eq!(Affine2x2::IDENTITY.map_f32(10.0, -10.0), (10.0, -10.0));
125 /// assert_eq!(Affine2x2::IDENTITY.map_f32(-2583.2, 1842.2), (-2583.2, 1842.2));
126 ///
127 /// let rot90 = Affine2x2::rotation_degrees(90.0);
128 /// assert_eq!(rot90.map_f32(23.5, 9.8), (9.8, -23.5));
129 /// ```
130 pub const fn map(&self, x: F2Dot14, y: F2Dot14) -> (F2Dot14, F2Dot14) {
131 let new_x = F2Dot14::saturating_maddp(self.xx, x, self.xy, y, F2Dot14::ZERO);
132 let new_y = F2Dot14::saturating_maddp(self.yx, x, self.yy, y, F2Dot14::ZERO);
133 (new_x, new_y)
134 }
135
136 /// Applies this [`Affine2x2`] transformation to the point
137 /// <span class="hidden">`[x, y]`</span>
138 /// <math><mo>\[</mo><mi>x</mi><mo> </mo><mi>y</mi><mo>\]</mo></math>.
139 pub const fn map_i16(&self, x: i16, y: i16) -> (i16, i16) {
140 // Cast X and Y to F2Dot14 and back. The results will be the same, since F2Dot14 is
141 // essentially just a wrapper over i16, and there's only scaling and no translation.
142 let (x, y) = self.map(F2Dot14::from_frac_num(x), F2Dot14::from_frac_num(y));
143 (x.frac_num(), y.frac_num())
144 }
145 /// Applies this [`Affine2x2`] transformation to the point
146 /// <span class="hidden">`[x, y]`</span>
147 /// <math><mo>\[</mo><mi>x</mi><mo> </mo><mi>y</mi><mo>\]</mo></math>.
148 pub const fn map_f32(&self, x: f32, y: f32) -> (f32, f32) {
149 (self.xx * x + self.xy * y, self.yx * x + self.yy * y)
150 }
151
152 // self × other
153 // [A B] × [a b] = [Aa+Bc Ab+Bd] = [xx*xx+yx*xy xx*yx+yx*yy]
154 // [C D] [c d] [Ca+Dc Cb+Dd] [xy*xx+yy*xy xy*yx+yy*yy]
155
156 /// Multiplies this [`Affine2x2`] by another, wrapping and truncating at [`F2Dot14`]'s bounds.
157 pub const fn wrapping_mul(self, rhs: Self) -> Self {
158 Self::new(
159 F2Dot14::wrapping_maddp(self.xx, rhs.xx, self.yx, rhs.xy, F2Dot14::ZERO),
160 F2Dot14::wrapping_maddp(self.xx, rhs.yx, self.yx, rhs.yy, F2Dot14::ZERO),
161 F2Dot14::wrapping_maddp(self.xy, rhs.xx, self.yy, rhs.xy, F2Dot14::ZERO),
162 F2Dot14::wrapping_maddp(self.xy, rhs.yx, self.yy, rhs.yy, F2Dot14::ZERO),
163 )
164 }
165 /// Multiplies this [`Affine2x2`] by another, saturating at [`F2Dot14`]'s bounds.
166 pub const fn saturating_mul(self, rhs: Self) -> Self {
167 Self::new(
168 F2Dot14::saturating_maddp(self.xx, rhs.xx, self.yx, rhs.xy, F2Dot14::ZERO),
169 F2Dot14::saturating_maddp(self.xx, rhs.yx, self.yx, rhs.yy, F2Dot14::ZERO),
170 F2Dot14::saturating_maddp(self.xy, rhs.xx, self.yy, rhs.xy, F2Dot14::ZERO),
171 F2Dot14::saturating_maddp(self.xy, rhs.yx, self.yy, rhs.yy, F2Dot14::ZERO),
172 )
173 }
174 /// Multiplies this [`Affine2x2`] by another, returning `None` if overflow occurs.
175 pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
176 Some(Self::new(
177 F2Dot14::checked_maddp(self.xx, rhs.xx, self.yx, rhs.xy, F2Dot14::ZERO)?,
178 F2Dot14::checked_maddp(self.xx, rhs.yx, self.yx, rhs.yy, F2Dot14::ZERO)?,
179 F2Dot14::checked_maddp(self.xy, rhs.xx, self.yy, rhs.xy, F2Dot14::ZERO)?,
180 F2Dot14::checked_maddp(self.xy, rhs.yx, self.yy, rhs.yy, F2Dot14::ZERO)?,
181 ))
182 }
183
184 /// Creates an [`Affine2x2`] from big-endian bytes.
185 pub const fn from_be_bytes(bytes: [u8; 8]) -> Self {
186 let (&[xx, yx, xy, yy], []) = bytes.as_chunks::<2>() else { panic!() };
187 Self::new(
188 F2Dot14::from_be_bytes(xx),
189 F2Dot14::from_be_bytes(yx),
190 F2Dot14::from_be_bytes(xy),
191 F2Dot14::from_be_bytes(yy),
192 )
193 }
194 /// Returns this [`Affine2x2`] as big-endian bytes.
195 pub const fn to_be_bytes(self) -> [u8; 8] {
196 let buf = [
197 self.xx.to_be_bytes(),
198 self.yx.to_be_bytes(),
199 self.xy.to_be_bytes(),
200 self.yy.to_be_bytes(),
201 ];
202 *buf.as_flattened().as_array().unwrap()
203 }
204
205 /// Returns this [`Affine2x2`]'s `(xx, yx, xy, yy)` as a tuple.
206 pub const fn to_tuple(self) -> (F2Dot14, F2Dot14, F2Dot14, F2Dot14) {
207 (self.xx, self.yx, self.xy, self.yy)
208 }
209 /// Returns this [`Affine2x2`]'s `(xx, yx, xy, yy)` as a tuple of [`f32`]s.
210 pub const fn to_tuple_f32(self) -> (f32, f32, f32, f32) {
211 (self.xx.get(), self.yx.get(), self.xy.get(), self.yy.get())
212 }
213
214 /// Creates an [`Affine2x2`] from a `[xx, yx, xy, yy]` array.
215 pub const fn from_array([xx, yx, xy, yy]: [F2Dot14; 4]) -> Self {
216 Self { xx, yx, xy, yy }
217 }
218 /// Returns this [`Affine2x2`]'s `[xx, yx, xy, yy]` as an array.
219 pub const fn to_array(self) -> [F2Dot14; 4] {
220 [self.xx, self.yx, self.xy, self.yy]
221 }
222}
223
224/// Performs multiplication `*` (panics on overflow in debug configuration).
225const impl std::ops::Mul for Affine2x2 {
226 type Output = Self;
227 fn mul(self, rhs: Self) -> Self::Output {
228 #[cfg(debug_assertions)]
229 return self.checked_mul(rhs).expect("attempt to multiply with overflow");
230 #[cfg(not(debug_assertions))]
231 return self.wrapping_mul(rhs);
232 }
233}
234
235impl std::fmt::Debug for Affine2x2 {
236 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
237 let Self { xx, yx, xy, yy } = *self;
238 write!(f, "[{:?}, {:?}; {:?}, {:?}]", xx, yx, xy, yy)
239 }
240}