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}