Skip to main content

ferroui_base/
pixel_point.rs

1//! Represents a point in device pixels.
2
3use std::fmt;
4use std::ops::{Add, AddAssign, Sub, SubAssign};
5use std::str::FromStr;
6
7use crate::utilities::{FormatError, SpanStringTokenizer};
8use crate::{PixelVector, Point, Vector};
9
10/// Represents a point in device pixels.
11///
12/// Integer arithmetic wraps on overflow. Conversions from floating point truncate
13/// toward zero and saturate at the `i32` range (NaN becomes 0).
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
15pub struct PixelPoint {
16    /// The X co-ordinate.
17    pub x: i32,
18    /// The Y co-ordinate.
19    pub y: i32,
20}
21
22impl PixelPoint {
23    /// A point representing 0,0.
24    pub const ORIGIN: PixelPoint = PixelPoint::new(0, 0);
25
26    /// Initializes a new instance of the [`PixelPoint`] structure.
27    #[inline]
28    pub const fn new(x: i32, y: i32) -> Self {
29        Self { x, y }
30    }
31
32    /// Parses a [`PixelPoint`] string (`"x, y"`).
33    pub fn parse(s: &str) -> Result<Self, FormatError> {
34        SpanStringTokenizer::with_message(s, "Invalid PixelPoint.")
35            .scope(|t| Ok(PixelPoint::new(t.read_int32()?, t.read_int32()?)))
36    }
37
38    /// Returns a boolean indicating whether the point is equal to the other given point.
39    #[inline]
40    pub const fn equals(&self, other: PixelPoint) -> bool {
41        self.x == other.x && self.y == other.y
42    }
43
44    /// Returns a new [`PixelPoint`] with the same Y co-ordinate and the specified X co-ordinate.
45    #[inline]
46    pub const fn with_x(&self, x: i32) -> PixelPoint {
47        PixelPoint::new(x, self.y)
48    }
49
50    /// Returns a new [`PixelPoint`] with the same X co-ordinate and the specified Y co-ordinate.
51    #[inline]
52    pub const fn with_y(&self, y: i32) -> PixelPoint {
53        PixelPoint::new(self.x, y)
54    }
55
56    /// Converts the [`PixelPoint`] to a device-independent [`Point`] using the
57    /// specified scaling factor.
58    #[inline]
59    pub fn to_point(&self, scale: f64) -> Point {
60        Point::new(self.x as f64 / scale, self.y as f64 / scale)
61    }
62
63    /// Converts the [`PixelPoint`] to a device-independent [`Point`] using the
64    /// specified per-axis scaling factor.
65    #[inline]
66    pub fn to_point_vector(&self, scale: Vector) -> Point {
67        Point::new(self.x as f64 / scale.x, self.y as f64 / scale.y)
68    }
69
70    /// Converts the [`PixelPoint`] to a device-independent [`Point`] using the
71    /// specified dots per inch (DPI).
72    #[inline]
73    pub fn to_point_with_dpi(&self, dpi: f64) -> Point {
74        self.to_point(dpi / 96.0)
75    }
76
77    /// Converts the [`PixelPoint`] to a device-independent [`Point`] using the
78    /// specified per-axis dots per inch (DPI).
79    #[inline]
80    pub fn to_point_with_dpi_vector(&self, dpi: Vector) -> Point {
81        self.to_point_vector(Vector::new(dpi.x / 96.0, dpi.y / 96.0))
82    }
83
84    /// Converts a [`Point`] to device pixels using the specified scaling factor.
85    #[inline]
86    pub fn from_point(point: Point, scale: f64) -> PixelPoint {
87        PixelPoint::new((point.x * scale) as i32, (point.y * scale) as i32)
88    }
89
90    /// Converts a [`Point`] to device pixels using the specified per-axis scaling factor.
91    #[inline]
92    pub fn from_point_vector(point: Point, scale: Vector) -> PixelPoint {
93        PixelPoint::new((point.x * scale.x) as i32, (point.y * scale.y) as i32)
94    }
95
96    /// Converts a [`Point`] to device pixels using the specified dots per inch (DPI).
97    #[inline]
98    pub fn from_point_with_dpi(point: Point, dpi: f64) -> PixelPoint {
99        PixelPoint::from_point(point, dpi / 96.0)
100    }
101
102    /// Converts a [`Point`] to device pixels using the specified per-axis dots per inch (DPI).
103    #[inline]
104    pub fn from_point_with_dpi_vector(point: Point, dpi: Vector) -> PixelPoint {
105        PixelPoint::from_point_vector(point, Vector::new(dpi.x / 96.0, dpi.y / 96.0))
106    }
107}
108
109/// Converts the [`PixelPoint`] to a [`PixelVector`].
110impl From<PixelPoint> for PixelVector {
111    #[inline]
112    fn from(p: PixelPoint) -> PixelVector {
113        PixelVector::new(p.x, p.y)
114    }
115}
116
117impl Add for PixelPoint {
118    type Output = PixelPoint;
119    #[inline]
120    fn add(self, b: PixelPoint) -> PixelPoint {
121        PixelPoint::new(self.x.wrapping_add(b.x), self.y.wrapping_add(b.y))
122    }
123}
124
125impl Add<PixelVector> for PixelPoint {
126    type Output = PixelPoint;
127    #[inline]
128    fn add(self, b: PixelVector) -> PixelPoint {
129        PixelPoint::new(self.x.wrapping_add(b.x), self.y.wrapping_add(b.y))
130    }
131}
132
133impl Sub for PixelPoint {
134    type Output = PixelPoint;
135    #[inline]
136    fn sub(self, b: PixelPoint) -> PixelPoint {
137        PixelPoint::new(self.x.wrapping_sub(b.x), self.y.wrapping_sub(b.y))
138    }
139}
140
141impl Sub<PixelVector> for PixelPoint {
142    type Output = PixelPoint;
143    #[inline]
144    fn sub(self, b: PixelVector) -> PixelPoint {
145        PixelPoint::new(self.x.wrapping_sub(b.x), self.y.wrapping_sub(b.y))
146    }
147}
148
149impl AddAssign for PixelPoint {
150    #[inline]
151    fn add_assign(&mut self, b: PixelPoint) {
152        *self = *self + b;
153    }
154}
155
156impl AddAssign<PixelVector> for PixelPoint {
157    #[inline]
158    fn add_assign(&mut self, b: PixelVector) {
159        *self = *self + b;
160    }
161}
162
163impl SubAssign for PixelPoint {
164    #[inline]
165    fn sub_assign(&mut self, b: PixelPoint) {
166        *self = *self - b;
167    }
168}
169
170impl SubAssign<PixelVector> for PixelPoint {
171    #[inline]
172    fn sub_assign(&mut self, b: PixelVector) {
173        *self = *self - b;
174    }
175}
176
177impl FromStr for PixelPoint {
178    type Err = FormatError;
179    #[inline]
180    fn from_str(s: &str) -> Result<Self, FormatError> {
181        PixelPoint::parse(s)
182    }
183}
184
185impl fmt::Display for PixelPoint {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        write!(f, "{}, {}", self.x, self.y)
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    // The reference test-suite has no tests for this type.
194    use super::*;
195
196    #[test]
197    fn parse_display_and_operators() {
198        assert_eq!(
199            PixelPoint::parse("10, -20").unwrap(),
200            PixelPoint::new(10, -20)
201        );
202        assert_eq!(
203            PixelPoint::parse("1.5, 2").unwrap_err().message(),
204            "Invalid PixelPoint."
205        );
206        assert_eq!(PixelPoint::new(10, -20).to_string(), "10, -20");
207        let p = PixelPoint::new(1, 2);
208        assert_eq!(p + PixelPoint::new(1, 1), PixelPoint::new(2, 3));
209        assert_eq!(p + PixelVector::new(1, 1), PixelPoint::new(2, 3));
210        assert_eq!(p - PixelPoint::new(1, 1), PixelPoint::new(0, 1));
211        assert_eq!(p - PixelVector::new(1, 1), PixelPoint::new(0, 1));
212        assert_eq!(PixelVector::from(p), PixelVector::new(1, 2));
213        assert_eq!(
214            PixelPoint::new(i32::MAX, 0) + PixelPoint::new(1, 0),
215            PixelPoint::new(i32::MIN, 0)
216        );
217    }
218
219    #[test]
220    fn conversions_truncate_toward_zero() {
221        assert_eq!(
222            PixelPoint::from_point(Point::new(1.9, -1.9), 1.0),
223            PixelPoint::new(1, -1)
224        );
225        assert_eq!(
226            PixelPoint::from_point_with_dpi(Point::new(10.0, 10.0), 144.0),
227            PixelPoint::new(15, 15)
228        );
229        assert_eq!(
230            PixelPoint::from_point_vector(Point::new(10.0, 10.0), Vector::new(2.0, 3.0)),
231            PixelPoint::new(20, 30)
232        );
233        assert_eq!(
234            PixelPoint::new(15, 30).to_point(1.5),
235            Point::new(10.0, 20.0)
236        );
237        assert_eq!(
238            PixelPoint::new(15, 30).to_point_with_dpi_vector(Vector::new(144.0, 192.0)),
239            Point::new(10.0, 15.0)
240        );
241    }
242}