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}