kurbo/
size.rs

1// Copyright 2019 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A 2D size.
5
6use core::fmt;
7use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
8
9use crate::common::FloatExt;
10use crate::{Rect, RoundedRect, RoundedRectRadii, Vec2};
11
12#[cfg(not(feature = "std"))]
13use crate::common::FloatFuncs;
14
15/// A 2D size.
16#[derive(Clone, Copy, Default, PartialEq)]
17#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct Size {
20    /// The width.
21    pub width: f64,
22    /// The height.
23    pub height: f64,
24}
25
26impl Size {
27    /// A size with zero width or height.
28    pub const ZERO: Size = Size::new(0., 0.);
29
30    /// A size with width and height set to `f64::INFINITY`.
31    pub const INFINITY: Size = Size::new(f64::INFINITY, f64::INFINITY);
32
33    /// Create a new `Size` with the provided `width` and `height`.
34    #[inline(always)]
35    pub const fn new(width: f64, height: f64) -> Self {
36        Size { width, height }
37    }
38
39    /// Returns the max of `width` and `height`.
40    ///
41    /// # Examples
42    ///
43    /// ```
44    /// use kurbo::Size;
45    /// let size = Size::new(-10.5, 42.0);
46    /// assert_eq!(size.max_side(), 42.0);
47    /// ```
48    pub fn max_side(self) -> f64 {
49        self.width.max(self.height)
50    }
51
52    /// Returns the min of `width` and `height`.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use kurbo::Size;
58    /// let size = Size::new(-10.5, 42.0);
59    /// assert_eq!(size.min_side(), -10.5);
60    /// ```
61    pub fn min_side(self) -> f64 {
62        self.width.min(self.height)
63    }
64
65    /// The area covered by this size.
66    #[inline]
67    pub fn area(self) -> f64 {
68        self.width * self.height
69    }
70
71    /// Whether this size has zero area.
72    #[doc(alias = "is_empty")]
73    #[inline]
74    pub fn is_zero_area(self) -> bool {
75        self.area() == 0.0
76    }
77
78    /// Whether this size has zero area.
79    ///
80    /// Note: a size with negative area is not considered empty.
81    #[inline]
82    #[deprecated(since = "0.11.1", note = "use is_zero_area instead")]
83    pub fn is_empty(self) -> bool {
84        self.is_zero_area()
85    }
86
87    /// Returns the component-wise minimum of `self` and `other`.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use kurbo::Size;
93    ///
94    /// let this = Size::new(0., 100.);
95    /// let other = Size::new(10., 10.);
96    ///
97    /// assert_eq!(this.min(other), Size::new(0., 10.));
98    /// ```
99    pub fn min(self, other: Size) -> Self {
100        Size {
101            width: self.width.min(other.width),
102            height: self.height.min(other.height),
103        }
104    }
105
106    /// Returns the component-wise maximum of `self` and `other`.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use kurbo::Size;
112    ///
113    /// let this = Size::new(0., 100.);
114    /// let other = Size::new(10., 10.);
115    ///
116    /// assert_eq!(this.max(other), Size::new(10., 100.));
117    /// ```
118    pub fn max(self, other: Size) -> Self {
119        Size {
120            width: self.width.max(other.width),
121            height: self.height.max(other.height),
122        }
123    }
124
125    /// Returns a new size bounded by `min` and `max.`
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// use kurbo::Size;
131    ///
132    /// let this = Size::new(0., 100.);
133    /// let min = Size::new(10., 10.,);
134    /// let max = Size::new(50., 50.);
135    /// assert_eq!(this.clamp(min, max), Size::new(10., 50.))
136    /// ```
137    pub fn clamp(self, min: Size, max: Size) -> Self {
138        self.max(min).min(max)
139    }
140
141    /// Convert this size into a [`Vec2`], with `width` mapped to `x` and `height`
142    /// mapped to `y`.
143    #[inline(always)]
144    pub const fn to_vec2(self) -> Vec2 {
145        Vec2::new(self.width, self.height)
146    }
147
148    /// Returns a new `Size`,
149    /// with `width` and `height` [rounded] to the nearest integer.
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// use kurbo::Size;
155    /// let size_pos = Size::new(3.3, 3.6).round();
156    /// assert_eq!(size_pos.width, 3.0);
157    /// assert_eq!(size_pos.height, 4.0);
158    /// let size_neg = Size::new(-3.3, -3.6).round();
159    /// assert_eq!(size_neg.width, -3.0);
160    /// assert_eq!(size_neg.height, -4.0);
161    /// ```
162    ///
163    /// [rounded]: f64::round
164    #[inline]
165    pub fn round(self) -> Size {
166        Size::new(self.width.round(), self.height.round())
167    }
168
169    /// Returns a new `Size`,
170    /// with `width` and `height` [rounded up] to the nearest integer,
171    /// unless they are already an integer.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use kurbo::Size;
177    /// let size_pos = Size::new(3.3, 3.6).ceil();
178    /// assert_eq!(size_pos.width, 4.0);
179    /// assert_eq!(size_pos.height, 4.0);
180    /// let size_neg = Size::new(-3.3, -3.6).ceil();
181    /// assert_eq!(size_neg.width, -3.0);
182    /// assert_eq!(size_neg.height, -3.0);
183    /// ```
184    ///
185    /// [rounded up]: f64::ceil
186    #[inline]
187    pub fn ceil(self) -> Size {
188        Size::new(self.width.ceil(), self.height.ceil())
189    }
190
191    /// Returns a new `Size`,
192    /// with `width` and `height` [rounded down] to the nearest integer,
193    /// unless they are already an integer.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use kurbo::Size;
199    /// let size_pos = Size::new(3.3, 3.6).floor();
200    /// assert_eq!(size_pos.width, 3.0);
201    /// assert_eq!(size_pos.height, 3.0);
202    /// let size_neg = Size::new(-3.3, -3.6).floor();
203    /// assert_eq!(size_neg.width, -4.0);
204    /// assert_eq!(size_neg.height, -4.0);
205    /// ```
206    ///
207    /// [rounded down]: f64::floor
208    #[inline]
209    pub fn floor(self) -> Size {
210        Size::new(self.width.floor(), self.height.floor())
211    }
212
213    /// Returns a new `Size`,
214    /// with `width` and `height` [rounded away] from zero to the nearest integer,
215    /// unless they are already an integer.
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// use kurbo::Size;
221    /// let size_pos = Size::new(3.3, 3.6).expand();
222    /// assert_eq!(size_pos.width, 4.0);
223    /// assert_eq!(size_pos.height, 4.0);
224    /// let size_neg = Size::new(-3.3, -3.6).expand();
225    /// assert_eq!(size_neg.width, -4.0);
226    /// assert_eq!(size_neg.height, -4.0);
227    /// ```
228    ///
229    /// [rounded away]: FloatExt::expand
230    #[inline]
231    pub fn expand(self) -> Size {
232        Size::new(self.width.expand(), self.height.expand())
233    }
234
235    /// Returns a new `Size`,
236    /// with `width` and `height` [rounded towards] zero to the nearest integer,
237    /// unless they are already an integer.
238    ///
239    /// # Examples
240    ///
241    /// ```
242    /// use kurbo::Size;
243    /// let size_pos = Size::new(3.3, 3.6).trunc();
244    /// assert_eq!(size_pos.width, 3.0);
245    /// assert_eq!(size_pos.height, 3.0);
246    /// let size_neg = Size::new(-3.3, -3.6).trunc();
247    /// assert_eq!(size_neg.width, -3.0);
248    /// assert_eq!(size_neg.height, -3.0);
249    /// ```
250    ///
251    /// [rounded towards]: f64::trunc
252    #[inline]
253    pub fn trunc(self) -> Size {
254        Size::new(self.width.trunc(), self.height.trunc())
255    }
256
257    /// Returns the aspect ratio of a rectangle with the given size.
258    ///
259    /// If the width is `0`, the output will be `sign(self.height) * infinity`. If The width and
260    /// height are `0`, then the output will be `NaN`.
261    pub fn aspect_ratio(self) -> f64 {
262        self.height / self.width
263    }
264
265    /// Convert this `Size` into a [`Rect`] with origin `(0.0, 0.0)`.
266    #[inline(always)]
267    pub const fn to_rect(self) -> Rect {
268        Rect::new(0., 0., self.width, self.height)
269    }
270
271    /// Convert this `Size` into a [`RoundedRect`] with origin `(0.0, 0.0)` and
272    /// the provided corner radius.
273    #[inline]
274    pub fn to_rounded_rect(self, radii: impl Into<RoundedRectRadii>) -> RoundedRect {
275        self.to_rect().to_rounded_rect(radii)
276    }
277
278    /// Is this size [finite]?
279    ///
280    /// [finite]: f64::is_finite
281    #[inline]
282    pub fn is_finite(self) -> bool {
283        self.width.is_finite() && self.height.is_finite()
284    }
285
286    /// Is this size [NaN]?
287    ///
288    /// [NaN]: f64::is_nan
289    #[inline]
290    pub fn is_nan(self) -> bool {
291        self.width.is_nan() || self.height.is_nan()
292    }
293}
294
295impl fmt::Debug for Size {
296    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
297        write!(f, "{:?}W×{:?}H", self.width, self.height)
298    }
299}
300
301impl fmt::Display for Size {
302    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
303        write!(formatter, "(")?;
304        fmt::Display::fmt(&self.width, formatter)?;
305        write!(formatter, "×")?;
306        fmt::Display::fmt(&self.height, formatter)?;
307        write!(formatter, ")")
308    }
309}
310
311impl MulAssign<f64> for Size {
312    #[inline]
313    fn mul_assign(&mut self, other: f64) {
314        *self = Size {
315            width: self.width * other,
316            height: self.height * other,
317        };
318    }
319}
320
321impl Mul<Size> for f64 {
322    type Output = Size;
323
324    #[inline]
325    fn mul(self, other: Size) -> Size {
326        other * self
327    }
328}
329
330impl Mul<f64> for Size {
331    type Output = Size;
332
333    #[inline]
334    fn mul(self, other: f64) -> Size {
335        Size {
336            width: self.width * other,
337            height: self.height * other,
338        }
339    }
340}
341
342impl DivAssign<f64> for Size {
343    #[inline]
344    fn div_assign(&mut self, other: f64) {
345        *self = Size {
346            width: self.width / other,
347            height: self.height / other,
348        };
349    }
350}
351
352impl Div<f64> for Size {
353    type Output = Size;
354
355    #[inline]
356    fn div(self, other: f64) -> Size {
357        Size {
358            width: self.width / other,
359            height: self.height / other,
360        }
361    }
362}
363
364impl Add<Size> for Size {
365    type Output = Size;
366    #[inline]
367    fn add(self, other: Size) -> Size {
368        Size {
369            width: self.width + other.width,
370            height: self.height + other.height,
371        }
372    }
373}
374
375impl AddAssign<Size> for Size {
376    #[inline]
377    fn add_assign(&mut self, other: Size) {
378        *self = *self + other;
379    }
380}
381
382impl Sub<Size> for Size {
383    type Output = Size;
384    #[inline]
385    fn sub(self, other: Size) -> Size {
386        Size {
387            width: self.width - other.width,
388            height: self.height - other.height,
389        }
390    }
391}
392
393impl SubAssign<Size> for Size {
394    #[inline]
395    fn sub_assign(&mut self, other: Size) {
396        *self = *self - other;
397    }
398}
399
400impl From<(f64, f64)> for Size {
401    #[inline(always)]
402    fn from(v: (f64, f64)) -> Size {
403        Size {
404            width: v.0,
405            height: v.1,
406        }
407    }
408}
409
410impl From<Size> for (f64, f64) {
411    #[inline(always)]
412    fn from(v: Size) -> (f64, f64) {
413        (v.width, v.height)
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420
421    #[test]
422    fn display() {
423        let s = Size::new(-0.12345, 9.87654);
424        assert_eq!(format!("{s}"), "(-0.12345×9.87654)");
425
426        let s = Size::new(-0.12345, 9.87654);
427        assert_eq!(format!("{s:+6.2}"), "( -0.12× +9.88)");
428    }
429
430    #[test]
431    fn aspect_ratio() {
432        let s = Size::new(1.0, 1.0);
433        assert!((s.aspect_ratio() - 1.0).abs() < 1e-6);
434    }
435}