euclid/scale.rs
1// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9//! A type-checked scaling factor between units.
10
11use crate::num::One;
12
13use crate::{Point2D, Point3D, Rect, Size2D, Vector2D, Box2D, Box3D};
14use core::cmp::Ordering;
15use core::fmt;
16use core::hash::{Hash, Hasher};
17use core::marker::PhantomData;
18use core::ops::{Add, Div, Mul, Sub};
19use num_traits::NumCast;
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22
23/// A scaling factor between two different units of measurement.
24///
25/// This is effectively a type-safe float, intended to be used in combination with other types like
26/// `length::Length` to enforce conversion between systems of measurement at compile time.
27///
28/// `Src` and `Dst` represent the units before and after multiplying a value by a `Scale`. They
29/// may be types without values, such as empty enums. For example:
30///
31/// ```rust
32/// use euclid::Scale;
33/// use euclid::Length;
34/// enum Mm {};
35/// enum Inch {};
36///
37/// let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4);
38///
39/// let one_foot: Length<f32, Inch> = Length::new(12.0);
40/// let one_foot_in_mm: Length<f32, Mm> = one_foot * mm_per_inch;
41/// ```
42#[repr(C)]
43#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
44#[cfg_attr(
45 feature = "serde",
46 serde(bound(
47 serialize = "T: serde::Serialize",
48 deserialize = "T: serde::Deserialize<'de>"
49 ))
50)]
51pub struct Scale<T, Src, Dst>(pub T, #[doc(hidden)] pub PhantomData<(Src, Dst)>);
52
53impl<T, Src, Dst> Scale<T, Src, Dst> {
54 #[inline]
55 pub const fn new(x: T) -> Self {
56 Scale(x, PhantomData)
57 }
58
59 /// Creates an identity scale (1.0).
60 #[inline]
61 pub fn identity() -> Self
62 where
63 T: One
64 {
65 Scale::new(T::one())
66 }
67
68 /// Returns the given point transformed by this scale.
69 ///
70 /// # Example
71 ///
72 /// ```rust
73 /// use euclid::{Scale, point2};
74 /// enum Mm {};
75 /// enum Cm {};
76 ///
77 /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
78 ///
79 /// assert_eq!(to_mm.transform_point(point2(42, -42)), point2(420, -420));
80 /// ```
81 #[inline]
82 pub fn transform_point(self, point: Point2D<T, Src>) -> Point2D<T::Output, Dst>
83 where
84 T: Copy + Mul,
85 {
86 Point2D::new(point.x * self.0, point.y * self.0)
87 }
88
89 /// Returns the given point transformed by this scale.
90 #[inline]
91 pub fn transform_point3d(self, point: Point3D<T, Src>) -> Point3D<T::Output, Dst>
92 where
93 T: Copy + Mul,
94 {
95 Point3D::new(point.x * self.0, point.y * self.0, point.z * self.0)
96 }
97
98 /// Returns the given vector transformed by this scale.
99 ///
100 /// # Example
101 ///
102 /// ```rust
103 /// use euclid::{Scale, vec2};
104 /// enum Mm {};
105 /// enum Cm {};
106 ///
107 /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
108 ///
109 /// assert_eq!(to_mm.transform_vector(vec2(42, -42)), vec2(420, -420));
110 /// ```
111 #[inline]
112 pub fn transform_vector(self, vec: Vector2D<T, Src>) -> Vector2D<T::Output, Dst>
113 where
114 T: Copy + Mul,
115 {
116 Vector2D::new(vec.x * self.0, vec.y * self.0)
117 }
118
119 /// Returns the given vector transformed by this scale.
120 ///
121 /// # Example
122 ///
123 /// ```rust
124 /// use euclid::{Scale, size2};
125 /// enum Mm {};
126 /// enum Cm {};
127 ///
128 /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
129 ///
130 /// assert_eq!(to_mm.transform_size(size2(42, -42)), size2(420, -420));
131 /// ```
132 #[inline]
133 pub fn transform_size(self, size: Size2D<T, Src>) -> Size2D<T::Output, Dst>
134 where
135 T: Copy + Mul,
136 {
137 Size2D::new(size.width * self.0, size.height * self.0)
138 }
139
140 /// Returns the given rect transformed by this scale.
141 ///
142 /// # Example
143 ///
144 /// ```rust
145 /// use euclid::{Scale, rect};
146 /// enum Mm {};
147 /// enum Cm {};
148 ///
149 /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
150 ///
151 /// assert_eq!(to_mm.transform_rect(&rect(1, 2, 42, -42)), rect(10, 20, 420, -420));
152 /// ```
153 #[inline]
154 pub fn transform_rect(self, rect: &Rect<T, Src>) -> Rect<T::Output, Dst>
155 where
156 T: Copy + Mul,
157 {
158 Rect::new(
159 self.transform_point(rect.origin),
160 self.transform_size(rect.size),
161 )
162 }
163
164 /// Returns the given box transformed by this scale.
165 #[inline]
166 pub fn transform_box2d(self, b: &Box2D<T, Src>) -> Box2D<T::Output, Dst>
167 where
168 T: Copy + Mul,
169 {
170 Box2D {
171 min: self.transform_point(b.min),
172 max: self.transform_point(b.max),
173 }
174 }
175
176 /// Returns the given box transformed by this scale.
177 #[inline]
178 pub fn transform_box3d(self, b: &Box3D<T, Src>) -> Box3D<T::Output, Dst>
179 where
180 T: Copy + Mul,
181 {
182 Box3D {
183 min: self.transform_point3d(b.min),
184 max: self.transform_point3d(b.max),
185 }
186 }
187
188 /// Returns `true` if this scale has no effect.
189 ///
190 /// # Example
191 ///
192 /// ```rust
193 /// use euclid::Scale;
194 /// use euclid::num::One;
195 /// enum Mm {};
196 /// enum Cm {};
197 ///
198 /// let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
199 /// let mm_per_mm: Scale<f32, Mm, Mm> = Scale::new(1.0);
200 ///
201 /// assert_eq!(cm_per_mm.is_identity(), false);
202 /// assert_eq!(mm_per_mm.is_identity(), true);
203 /// assert_eq!(mm_per_mm, Scale::one());
204 /// ```
205 #[inline]
206 pub fn is_identity(self) -> bool
207 where
208 T: PartialEq + One,
209 {
210 self.0 == T::one()
211 }
212
213 /// Returns the underlying scalar scale factor.
214 #[inline]
215 pub fn get(self) -> T {
216 self.0
217 }
218
219 /// The inverse Scale (1.0 / self).
220 ///
221 /// # Example
222 ///
223 /// ```rust
224 /// use euclid::Scale;
225 /// enum Mm {};
226 /// enum Cm {};
227 ///
228 /// let cm_per_mm: Scale<f32, Cm, Mm> = Scale::new(0.1);
229 ///
230 /// assert_eq!(cm_per_mm.inverse(), Scale::new(10.0));
231 /// ```
232 pub fn inverse(self) -> Scale<T::Output, Dst, Src>
233 where
234 T: One + Div,
235 {
236 let one: T = One::one();
237 Scale::new(one / self.0)
238 }
239}
240
241impl<T: NumCast, Src, Dst> Scale<T, Src, Dst> {
242 /// Cast from one numeric representation to another, preserving the units.
243 ///
244 /// # Panics
245 ///
246 /// If the source value cannot be represented by the target type `NewT`, then
247 /// method panics. Use `try_cast` if that must be case.
248 ///
249 /// # Example
250 ///
251 /// ```rust
252 /// use euclid::Scale;
253 /// enum Mm {};
254 /// enum Cm {};
255 ///
256 /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
257 ///
258 /// assert_eq!(to_mm.cast::<f32>(), Scale::new(10.0));
259 /// ```
260 /// That conversion will panic, because `i32` not enough to store such big numbers:
261 /// ```rust,should_panic
262 /// use euclid::Scale;
263 /// enum Mm {};// millimeter = 10^-2 meters
264 /// enum Em {};// exameter = 10^18 meters
265 ///
266 /// // Panics
267 /// let to_em: Scale<i32, Mm, Em> = Scale::new(10e20).cast();
268 /// ```
269 #[inline]
270 pub fn cast<NewT: NumCast>(self) -> Scale<NewT, Src, Dst> {
271 self.try_cast().unwrap()
272 }
273
274 /// Fallible cast from one numeric representation to another, preserving the units.
275 /// If the source value cannot be represented by the target type `NewT`, then `None`
276 /// is returned.
277 ///
278 /// # Example
279 ///
280 /// ```rust
281 /// use euclid::Scale;
282 /// enum Mm {};
283 /// enum Cm {};
284 /// enum Em {};// Exameter = 10^18 meters
285 ///
286 /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
287 /// let to_em: Scale<f32, Mm, Em> = Scale::new(10e20);
288 ///
289 /// assert_eq!(to_mm.try_cast::<f32>(), Some(Scale::new(10.0)));
290 /// // Integer to small to store that number
291 /// assert_eq!(to_em.try_cast::<i32>(), None);
292 /// ```
293 pub fn try_cast<NewT: NumCast>(self) -> Option<Scale<NewT, Src, Dst>> {
294 NumCast::from(self.0).map(Scale::new)
295 }
296}
297
298// scale0 * scale1
299// (A,B) * (B,C) = (A,C)
300impl<T: Mul, A, B, C> Mul<Scale<T, B, C>> for Scale<T, A, B> {
301 type Output = Scale<T::Output, A, C>;
302
303 #[inline]
304 fn mul(self, other: Scale<T, B, C>) -> Self::Output {
305 Scale::new(self.0 * other.0)
306 }
307}
308
309// scale0 + scale1
310impl<T: Add, Src, Dst> Add for Scale<T, Src, Dst> {
311 type Output = Scale<T::Output, Src, Dst>;
312
313 #[inline]
314 fn add(self, other: Scale<T, Src, Dst>) -> Self::Output {
315 Scale::new(self.0 + other.0)
316 }
317}
318
319// scale0 - scale1
320impl<T: Sub, Src, Dst> Sub for Scale<T, Src, Dst> {
321 type Output = Scale<T::Output, Src, Dst>;
322
323 #[inline]
324 fn sub(self, other: Scale<T, Src, Dst>) -> Self::Output {
325 Scale::new(self.0 - other.0)
326 }
327}
328
329// FIXME: Switch to `derive(PartialEq, Clone)` after this Rust issue is fixed:
330// https://github.com/rust-lang/rust/issues/26925
331
332impl<T: PartialEq, Src, Dst> PartialEq for Scale<T, Src, Dst> {
333 fn eq(&self, other: &Scale<T, Src, Dst>) -> bool {
334 self.0 == other.0
335 }
336}
337
338impl<T: Eq, Src, Dst> Eq for Scale<T, Src, Dst> {}
339
340impl<T: PartialOrd, Src, Dst> PartialOrd for Scale<T, Src, Dst> {
341 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
342 self.0.partial_cmp(&other.0)
343 }
344}
345
346impl<T: Ord, Src, Dst> Ord for Scale<T, Src, Dst> {
347 fn cmp(&self, other: &Self) -> Ordering {
348 self.0.cmp(&other.0)
349 }
350}
351
352impl<T: Clone, Src, Dst> Clone for Scale<T, Src, Dst> {
353 fn clone(&self) -> Scale<T, Src, Dst> {
354 Scale::new(self.0.clone())
355 }
356}
357
358impl<T: Copy, Src, Dst> Copy for Scale<T, Src, Dst> {}
359
360impl<T: fmt::Debug, Src, Dst> fmt::Debug for Scale<T, Src, Dst> {
361 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
362 self.0.fmt(f)
363 }
364}
365
366impl<T: Default, Src, Dst> Default for Scale<T, Src, Dst> {
367 fn default() -> Self {
368 Self::new(T::default())
369 }
370}
371
372impl<T: Hash, Src, Dst> Hash for Scale<T, Src, Dst> {
373 fn hash<H: Hasher>(&self, state: &mut H) {
374 self.0.hash(state)
375 }
376}
377
378impl<T: One, Src, Dst> One for Scale<T, Src, Dst> {
379 #[inline]
380 fn one() -> Self {
381 Scale::new(T::one())
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::Scale;
388
389 enum Inch {}
390 enum Cm {}
391 enum Mm {}
392
393 #[test]
394 fn test_scale() {
395 let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4);
396 let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
397
398 let mm_per_cm: Scale<f32, Cm, Mm> = cm_per_mm.inverse();
399 assert_eq!(mm_per_cm.get(), 10.0);
400
401 let one: Scale<f32, Mm, Mm> = cm_per_mm * mm_per_cm;
402 assert_eq!(one.get(), 1.0);
403
404 let one: Scale<f32, Cm, Cm> = mm_per_cm * cm_per_mm;
405 assert_eq!(one.get(), 1.0);
406
407 let cm_per_inch: Scale<f32, Inch, Cm> = mm_per_inch * cm_per_mm;
408 // mm cm cm
409 // ---- x ---- = ----
410 // inch mm inch
411 assert_eq!(cm_per_inch, Scale::new(2.54));
412
413 let a: Scale<isize, Inch, Inch> = Scale::new(2);
414 let b: Scale<isize, Inch, Inch> = Scale::new(3);
415 assert_ne!(a, b);
416 assert_eq!(a, a.clone());
417 assert_eq!(a.clone() + b.clone(), Scale::new(5));
418 assert_eq!(a - b, Scale::new(-1));
419 }
420}