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 num::One;
12
13use num_traits::NumCast;
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16use core::fmt;
17use core::ops::{Add, Div, Mul, Neg, Sub};
18use core::marker::PhantomData;
19use {TypedPoint2D, TypedRect, TypedSize2D, TypedVector2D};
20
21/// A scaling factor between two different units of measurement.
22///
23/// This is effectively a type-safe float, intended to be used in combination with other types like
24/// `length::Length` to enforce conversion between systems of measurement at compile time.
25///
26/// `Src` and `Dst` represent the units before and after multiplying a value by a `TypedScale`. They
27/// may be types without values, such as empty enums.  For example:
28///
29/// ```rust
30/// use euclid::TypedScale;
31/// use euclid::Length;
32/// enum Mm {};
33/// enum Inch {};
34///
35/// let mm_per_inch: TypedScale<f32, Inch, Mm> = TypedScale::new(25.4);
36///
37/// let one_foot: Length<f32, Inch> = Length::new(12.0);
38/// let one_foot_in_mm: Length<f32, Mm> = one_foot * mm_per_inch;
39/// ```
40#[repr(C)]
41pub struct TypedScale<T, Src, Dst>(pub T, #[doc(hidden)] pub PhantomData<(Src, Dst)>);
42
43#[cfg(feature = "serde")]
44impl<'de, T, Src, Dst> Deserialize<'de> for TypedScale<T, Src, Dst>
45where
46    T: Deserialize<'de>,
47{
48    fn deserialize<D>(deserializer: D) -> Result<TypedScale<T, Src, Dst>, D::Error>
49    where
50        D: Deserializer<'de>,
51    {
52        Ok(TypedScale(
53            try!(Deserialize::deserialize(deserializer)),
54            PhantomData,
55        ))
56    }
57}
58
59#[cfg(feature = "serde")]
60impl<T, Src, Dst> Serialize for TypedScale<T, Src, Dst>
61where
62    T: Serialize,
63{
64    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
65    where
66        S: Serializer,
67    {
68        self.0.serialize(serializer)
69    }
70}
71
72impl<T, Src, Dst> TypedScale<T, Src, Dst> {
73    pub fn new(x: T) -> Self {
74        TypedScale(x, PhantomData)
75    }
76}
77
78impl<T: Clone, Src, Dst> TypedScale<T, Src, Dst> {
79    pub fn get(&self) -> T {
80        self.0.clone()
81    }
82}
83
84impl<Src, Dst> TypedScale<f32, Src, Dst> {
85    /// Identity scaling, could be used to safely transit from one space to another.
86    pub const ONE: Self = TypedScale(1.0, PhantomData);
87}
88
89impl<T: Clone + One + Div<T, Output = T>, Src, Dst> TypedScale<T, Src, Dst> {
90    /// The inverse TypedScale (1.0 / self).
91    pub fn inv(&self) -> TypedScale<T, Dst, Src> {
92        let one: T = One::one();
93        TypedScale::new(one / self.get())
94    }
95}
96
97// scale0 * scale1
98impl<T: Clone + Mul<T, Output = T>, A, B, C> Mul<TypedScale<T, B, C>> for TypedScale<T, A, B> {
99    type Output = TypedScale<T, A, C>;
100    #[inline]
101    fn mul(self, other: TypedScale<T, B, C>) -> TypedScale<T, A, C> {
102        TypedScale::new(self.get() * other.get())
103    }
104}
105
106// scale0 + scale1
107impl<T: Clone + Add<T, Output = T>, Src, Dst> Add for TypedScale<T, Src, Dst> {
108    type Output = TypedScale<T, Src, Dst>;
109    #[inline]
110    fn add(self, other: TypedScale<T, Src, Dst>) -> TypedScale<T, Src, Dst> {
111        TypedScale::new(self.get() + other.get())
112    }
113}
114
115// scale0 - scale1
116impl<T: Clone + Sub<T, Output = T>, Src, Dst> Sub for TypedScale<T, Src, Dst> {
117    type Output = TypedScale<T, Src, Dst>;
118    #[inline]
119    fn sub(self, other: TypedScale<T, Src, Dst>) -> TypedScale<T, Src, Dst> {
120        TypedScale::new(self.get() - other.get())
121    }
122}
123
124impl<T: NumCast + Clone, Src, Dst0> TypedScale<T, Src, Dst0> {
125    /// Cast from one numeric representation to another, preserving the units.
126    pub fn cast<T1: NumCast + Clone>(&self) -> TypedScale<T1, Src, Dst0> {
127        self.try_cast().unwrap()
128    }
129
130    /// Fallible cast from one numeric representation to another, preserving the units.
131    pub fn try_cast<T1: NumCast + Clone>(&self) -> Option<TypedScale<T1, Src, Dst0>> {
132        NumCast::from(self.get()).map(TypedScale::new)
133    }
134}
135
136impl<T, Src, Dst> TypedScale<T, Src, Dst>
137where
138    T: Copy + Clone + Mul<T, Output = T> + Neg<Output = T> + PartialEq + One,
139{
140    /// Returns the given point transformed by this scale.
141    #[inline]
142    pub fn transform_point(&self, point: &TypedPoint2D<T, Src>) -> TypedPoint2D<T, Dst> {
143        TypedPoint2D::new(point.x * self.get(), point.y * self.get())
144    }
145
146    /// Returns the given vector transformed by this scale.
147    #[inline]
148    pub fn transform_vector(&self, vec: &TypedVector2D<T, Src>) -> TypedVector2D<T, Dst> {
149        TypedVector2D::new(vec.x * self.get(), vec.y * self.get())
150    }
151
152    /// Returns the given vector transformed by this scale.
153    #[inline]
154    pub fn transform_size(&self, size: &TypedSize2D<T, Src>) -> TypedSize2D<T, Dst> {
155        TypedSize2D::new(size.width * self.get(), size.height * self.get())
156    }
157
158    /// Returns the given rect transformed by this scale.
159    #[inline]
160    pub fn transform_rect(&self, rect: &TypedRect<T, Src>) -> TypedRect<T, Dst> {
161        TypedRect::new(
162            self.transform_point(&rect.origin),
163            self.transform_size(&rect.size),
164        )
165    }
166
167    /// Returns the inverse of this scale.
168    #[inline]
169    pub fn inverse(&self) -> TypedScale<T, Dst, Src> {
170        TypedScale::new(-self.get())
171    }
172
173    /// Returns true if this scale has no effect.
174    #[inline]
175    pub fn is_identity(&self) -> bool {
176        self.get() == T::one()
177    }
178}
179
180// FIXME: Switch to `derive(PartialEq, Clone)` after this Rust issue is fixed:
181// https://github.com/mozilla/rust/issues/7671
182
183impl<T: PartialEq, Src, Dst> PartialEq for TypedScale<T, Src, Dst> {
184    fn eq(&self, other: &TypedScale<T, Src, Dst>) -> bool {
185        self.0 == other.0
186    }
187}
188
189impl<T: Clone, Src, Dst> Clone for TypedScale<T, Src, Dst> {
190    fn clone(&self) -> TypedScale<T, Src, Dst> {
191        TypedScale::new(self.get())
192    }
193}
194
195impl<T: Copy, Src, Dst> Copy for TypedScale<T, Src, Dst> {}
196
197impl<T: fmt::Debug, Src, Dst> fmt::Debug for TypedScale<T, Src, Dst> {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        self.0.fmt(f)
200    }
201}
202
203impl<T: fmt::Display, Src, Dst> fmt::Display for TypedScale<T, Src, Dst> {
204    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205        self.0.fmt(f)
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::TypedScale;
212
213    enum Inch {}
214    enum Cm {}
215    enum Mm {}
216
217    #[test]
218    fn test_scale() {
219        let mm_per_inch: TypedScale<f32, Inch, Mm> = TypedScale::new(25.4);
220        let cm_per_mm: TypedScale<f32, Mm, Cm> = TypedScale::new(0.1);
221
222        let mm_per_cm: TypedScale<f32, Cm, Mm> = cm_per_mm.inv();
223        assert_eq!(mm_per_cm.get(), 10.0);
224
225        let cm_per_inch: TypedScale<f32, Inch, Cm> = mm_per_inch * cm_per_mm;
226        assert_eq!(cm_per_inch, TypedScale::new(2.54));
227
228        let a: TypedScale<isize, Inch, Inch> = TypedScale::new(2);
229        let b: TypedScale<isize, Inch, Inch> = TypedScale::new(3);
230        assert!(a != b);
231        assert_eq!(a, a.clone());
232        assert_eq!(a.clone() + b.clone(), TypedScale::new(5));
233        assert_eq!(a - b, TypedScale::new(-1));
234    }
235}