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