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