// Copyright 2014 The Servo Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A type-checked scaling factor between units. use crate::num::One; use crate::{Point2D, Point3D, Rect, Size2D, Vector2D, Box2D, Box3D}; use core::cmp::Ordering; use core::fmt; use core::hash::{Hash, Hasher}; use core::marker::PhantomData; use core::ops::{Add, Div, Mul, Sub}; use num_traits::NumCast; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "bytemuck")] use bytemuck::{Zeroable, Pod}; /// A scaling factor between two different units of measurement. /// /// This is effectively a type-safe float, intended to be used in combination with other types like /// `length::Length` to enforce conversion between systems of measurement at compile time. /// /// `Src` and `Dst` represent the units before and after multiplying a value by a `Scale`. They /// may be types without values, such as empty enums. For example: /// /// ```rust /// use euclid::Scale; /// use euclid::Length; /// enum Mm {}; /// enum Inch {}; /// /// let mm_per_inch: Scale = Scale::new(25.4); /// /// let one_foot: Length = Length::new(12.0); /// let one_foot_in_mm: Length = one_foot * mm_per_inch; /// ``` #[repr(C)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde", serde(bound( serialize = "T: serde::Serialize", deserialize = "T: serde::Deserialize<'de>" )) )] pub struct Scale(pub T, #[doc(hidden)] pub PhantomData<(Src, Dst)>); impl Scale { #[inline] pub const fn new(x: T) -> Self { Scale(x, PhantomData) } /// Creates an identity scale (1.0). #[inline] pub fn identity() -> Self where T: One { Scale::new(T::one()) } /// Returns the given point transformed by this scale. /// /// # Example /// /// ```rust /// use euclid::{Scale, point2}; /// enum Mm {}; /// enum Cm {}; /// /// let to_mm: Scale = Scale::new(10); /// /// assert_eq!(to_mm.transform_point(point2(42, -42)), point2(420, -420)); /// ``` #[inline] pub fn transform_point(self, point: Point2D) -> Point2D where T: Copy + Mul, { Point2D::new(point.x * self.0, point.y * self.0) } /// Returns the given point transformed by this scale. #[inline] pub fn transform_point3d(self, point: Point3D) -> Point3D where T: Copy + Mul, { Point3D::new(point.x * self.0, point.y * self.0, point.z * self.0) } /// Returns the given vector transformed by this scale. /// /// # Example /// /// ```rust /// use euclid::{Scale, vec2}; /// enum Mm {}; /// enum Cm {}; /// /// let to_mm: Scale = Scale::new(10); /// /// assert_eq!(to_mm.transform_vector(vec2(42, -42)), vec2(420, -420)); /// ``` #[inline] pub fn transform_vector(self, vec: Vector2D) -> Vector2D where T: Copy + Mul, { Vector2D::new(vec.x * self.0, vec.y * self.0) } /// Returns the given vector transformed by this scale. /// /// # Example /// /// ```rust /// use euclid::{Scale, size2}; /// enum Mm {}; /// enum Cm {}; /// /// let to_mm: Scale = Scale::new(10); /// /// assert_eq!(to_mm.transform_size(size2(42, -42)), size2(420, -420)); /// ``` #[inline] pub fn transform_size(self, size: Size2D) -> Size2D where T: Copy + Mul, { Size2D::new(size.width * self.0, size.height * self.0) } /// Returns the given rect transformed by this scale. /// /// # Example /// /// ```rust /// use euclid::{Scale, rect}; /// enum Mm {}; /// enum Cm {}; /// /// let to_mm: Scale = Scale::new(10); /// /// assert_eq!(to_mm.transform_rect(&rect(1, 2, 42, -42)), rect(10, 20, 420, -420)); /// ``` #[inline] pub fn transform_rect(self, rect: &Rect) -> Rect where T: Copy + Mul, { Rect::new( self.transform_point(rect.origin), self.transform_size(rect.size), ) } /// Returns the given box transformed by this scale. #[inline] pub fn transform_box2d(self, b: &Box2D) -> Box2D where T: Copy + Mul, { Box2D { min: self.transform_point(b.min), max: self.transform_point(b.max), } } /// Returns the given box transformed by this scale. #[inline] pub fn transform_box3d(self, b: &Box3D) -> Box3D where T: Copy + Mul, { Box3D { min: self.transform_point3d(b.min), max: self.transform_point3d(b.max), } } /// Returns `true` if this scale has no effect. /// /// # Example /// /// ```rust /// use euclid::Scale; /// use euclid::num::One; /// enum Mm {}; /// enum Cm {}; /// /// let cm_per_mm: Scale = Scale::new(0.1); /// let mm_per_mm: Scale = Scale::new(1.0); /// /// assert_eq!(cm_per_mm.is_identity(), false); /// assert_eq!(mm_per_mm.is_identity(), true); /// assert_eq!(mm_per_mm, Scale::one()); /// ``` #[inline] pub fn is_identity(self) -> bool where T: PartialEq + One, { self.0 == T::one() } /// Returns the underlying scalar scale factor. #[inline] pub fn get(self) -> T { self.0 } /// The inverse Scale (1.0 / self). /// /// # Example /// /// ```rust /// use euclid::Scale; /// enum Mm {}; /// enum Cm {}; /// /// let cm_per_mm: Scale = Scale::new(0.1); /// /// assert_eq!(cm_per_mm.inverse(), Scale::new(10.0)); /// ``` pub fn inverse(self) -> Scale where T: One + Div, { let one: T = One::one(); Scale::new(one / self.0) } } impl Scale { /// Cast from one numeric representation to another, preserving the units. /// /// # Panics /// /// If the source value cannot be represented by the target type `NewT`, then /// method panics. Use `try_cast` if that must be case. /// /// # Example /// /// ```rust /// use euclid::Scale; /// enum Mm {}; /// enum Cm {}; /// /// let to_mm: Scale = Scale::new(10); /// /// assert_eq!(to_mm.cast::(), Scale::new(10.0)); /// ``` /// That conversion will panic, because `i32` not enough to store such big numbers: /// ```rust,should_panic /// use euclid::Scale; /// enum Mm {};// millimeter = 10^-2 meters /// enum Em {};// exameter = 10^18 meters /// /// // Panics /// let to_em: Scale = Scale::new(10e20).cast(); /// ``` #[inline] pub fn cast(self) -> Scale { self.try_cast().unwrap() } /// Fallible cast from one numeric representation to another, preserving the units. /// If the source value cannot be represented by the target type `NewT`, then `None` /// is returned. /// /// # Example /// /// ```rust /// use euclid::Scale; /// enum Mm {}; /// enum Cm {}; /// enum Em {};// Exameter = 10^18 meters /// /// let to_mm: Scale = Scale::new(10); /// let to_em: Scale = Scale::new(10e20); /// /// assert_eq!(to_mm.try_cast::(), Some(Scale::new(10.0))); /// // Integer to small to store that number /// assert_eq!(to_em.try_cast::(), None); /// ``` pub fn try_cast(self) -> Option> { NumCast::from(self.0).map(Scale::new) } } #[cfg(feature = "bytemuck")] unsafe impl Zeroable for Scale {} #[cfg(feature = "bytemuck")] unsafe impl Pod for Scale {} // scale0 * scale1 // (A,B) * (B,C) = (A,C) impl Mul> for Scale { type Output = Scale; #[inline] fn mul(self, other: Scale) -> Self::Output { Scale::new(self.0 * other.0) } } // scale0 + scale1 impl Add for Scale { type Output = Scale; #[inline] fn add(self, other: Scale) -> Self::Output { Scale::new(self.0 + other.0) } } // scale0 - scale1 impl Sub for Scale { type Output = Scale; #[inline] fn sub(self, other: Scale) -> Self::Output { Scale::new(self.0 - other.0) } } // FIXME: Switch to `derive(PartialEq, Clone)` after this Rust issue is fixed: // https://github.com/rust-lang/rust/issues/26925 impl PartialEq for Scale { fn eq(&self, other: &Scale) -> bool { self.0 == other.0 } } impl Eq for Scale {} impl PartialOrd for Scale { fn partial_cmp(&self, other: &Self) -> Option { self.0.partial_cmp(&other.0) } } impl Ord for Scale { fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } } impl Clone for Scale { fn clone(&self) -> Scale { Scale::new(self.0.clone()) } } impl Copy for Scale {} impl fmt::Debug for Scale { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } } impl Default for Scale { fn default() -> Self { Self::new(T::default()) } } impl Hash for Scale { fn hash(&self, state: &mut H) { self.0.hash(state) } } impl One for Scale { #[inline] fn one() -> Self { Scale::new(T::one()) } } #[cfg(test)] mod tests { use super::Scale; enum Inch {} enum Cm {} enum Mm {} #[test] fn test_scale() { let mm_per_inch: Scale = Scale::new(25.4); let cm_per_mm: Scale = Scale::new(0.1); let mm_per_cm: Scale = cm_per_mm.inverse(); assert_eq!(mm_per_cm.get(), 10.0); let one: Scale = cm_per_mm * mm_per_cm; assert_eq!(one.get(), 1.0); let one: Scale = mm_per_cm * cm_per_mm; assert_eq!(one.get(), 1.0); let cm_per_inch: Scale = mm_per_inch * cm_per_mm; // mm cm cm // ---- x ---- = ---- // inch mm inch assert_eq!(cm_per_inch, Scale::new(2.54)); let a: Scale = Scale::new(2); let b: Scale = Scale::new(3); assert_ne!(a, b); assert_eq!(a, a.clone()); assert_eq!(a.clone() + b.clone(), Scale::new(5)); assert_eq!(a - b, Scale::new(-1)); } }