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::{Axis, 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 /// Returns the component-wise minimum of `self` and `other`.
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// use kurbo::Size;
84 ///
85 /// let this = Size::new(0., 100.);
86 /// let other = Size::new(10., 10.);
87 ///
88 /// assert_eq!(this.min(other), Size::new(0., 10.));
89 /// ```
90 pub fn min(self, other: Size) -> Self {
91 Size {
92 width: self.width.min(other.width),
93 height: self.height.min(other.height),
94 }
95 }
96
97 /// Returns the component-wise maximum of `self` and `other`.
98 ///
99 /// # Examples
100 ///
101 /// ```
102 /// use kurbo::Size;
103 ///
104 /// let this = Size::new(0., 100.);
105 /// let other = Size::new(10., 10.);
106 ///
107 /// assert_eq!(this.max(other), Size::new(10., 100.));
108 /// ```
109 pub fn max(self, other: Size) -> Self {
110 Size {
111 width: self.width.max(other.width),
112 height: self.height.max(other.height),
113 }
114 }
115
116 /// Returns a new size bounded by `min` and `max.`
117 ///
118 /// # Examples
119 ///
120 /// ```
121 /// use kurbo::Size;
122 ///
123 /// let this = Size::new(0., 100.);
124 /// let min = Size::new(10., 10.,);
125 /// let max = Size::new(50., 50.);
126 /// assert_eq!(this.clamp(min, max), Size::new(10., 50.))
127 /// ```
128 pub fn clamp(self, min: Size, max: Size) -> Self {
129 self.max(min).min(max)
130 }
131
132 /// Convert this size into a [`Vec2`], with `width` mapped to `x` and `height`
133 /// mapped to `y`.
134 #[inline(always)]
135 pub const fn to_vec2(self) -> Vec2 {
136 Vec2::new(self.width, self.height)
137 }
138
139 /// Returns a new `Size`,
140 /// with `width` and `height` [rounded] to the nearest integer.
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// use kurbo::Size;
146 /// let size_pos = Size::new(3.3, 3.6).round();
147 /// assert_eq!(size_pos.width, 3.0);
148 /// assert_eq!(size_pos.height, 4.0);
149 /// let size_neg = Size::new(-3.3, -3.6).round();
150 /// assert_eq!(size_neg.width, -3.0);
151 /// assert_eq!(size_neg.height, -4.0);
152 /// ```
153 ///
154 /// [rounded]: f64::round
155 #[inline]
156 pub fn round(self) -> Size {
157 Size::new(self.width.round(), self.height.round())
158 }
159
160 /// Returns a new `Size`,
161 /// with `width` and `height` [rounded up] to the nearest integer,
162 /// unless they are already an integer.
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// use kurbo::Size;
168 /// let size_pos = Size::new(3.3, 3.6).ceil();
169 /// assert_eq!(size_pos.width, 4.0);
170 /// assert_eq!(size_pos.height, 4.0);
171 /// let size_neg = Size::new(-3.3, -3.6).ceil();
172 /// assert_eq!(size_neg.width, -3.0);
173 /// assert_eq!(size_neg.height, -3.0);
174 /// ```
175 ///
176 /// [rounded up]: f64::ceil
177 #[inline]
178 pub fn ceil(self) -> Size {
179 Size::new(self.width.ceil(), self.height.ceil())
180 }
181
182 /// Returns a new `Size`,
183 /// with `width` and `height` [rounded down] to the nearest integer,
184 /// unless they are already an integer.
185 ///
186 /// # Examples
187 ///
188 /// ```
189 /// use kurbo::Size;
190 /// let size_pos = Size::new(3.3, 3.6).floor();
191 /// assert_eq!(size_pos.width, 3.0);
192 /// assert_eq!(size_pos.height, 3.0);
193 /// let size_neg = Size::new(-3.3, -3.6).floor();
194 /// assert_eq!(size_neg.width, -4.0);
195 /// assert_eq!(size_neg.height, -4.0);
196 /// ```
197 ///
198 /// [rounded down]: f64::floor
199 #[inline]
200 pub fn floor(self) -> Size {
201 Size::new(self.width.floor(), self.height.floor())
202 }
203
204 /// Returns a new `Size`,
205 /// with `width` and `height` [rounded away] from zero to the nearest integer,
206 /// unless they are already an integer.
207 ///
208 /// # Examples
209 ///
210 /// ```
211 /// use kurbo::Size;
212 /// let size_pos = Size::new(3.3, 3.6).expand();
213 /// assert_eq!(size_pos.width, 4.0);
214 /// assert_eq!(size_pos.height, 4.0);
215 /// let size_neg = Size::new(-3.3, -3.6).expand();
216 /// assert_eq!(size_neg.width, -4.0);
217 /// assert_eq!(size_neg.height, -4.0);
218 /// ```
219 ///
220 /// [rounded away]: FloatExt::expand
221 #[inline]
222 pub fn expand(self) -> Size {
223 Size::new(self.width.expand(), self.height.expand())
224 }
225
226 /// Returns a new `Size`,
227 /// with `width` and `height` [rounded towards] zero to the nearest integer,
228 /// unless they are already an integer.
229 ///
230 /// # Examples
231 ///
232 /// ```
233 /// use kurbo::Size;
234 /// let size_pos = Size::new(3.3, 3.6).trunc();
235 /// assert_eq!(size_pos.width, 3.0);
236 /// assert_eq!(size_pos.height, 3.0);
237 /// let size_neg = Size::new(-3.3, -3.6).trunc();
238 /// assert_eq!(size_neg.width, -3.0);
239 /// assert_eq!(size_neg.height, -3.0);
240 /// ```
241 ///
242 /// [rounded towards]: f64::trunc
243 #[inline]
244 pub fn trunc(self) -> Size {
245 Size::new(self.width.trunc(), self.height.trunc())
246 }
247
248 /// Returns the aspect ratio of a rectangle with this size.
249 ///
250 /// The aspect ratio is the ratio of the width to the height.
251 ///
252 /// If the height is `0`, the output will be `sign(self.width) * infinity`. If the width and
253 /// height are both `0`, then the output will be `NaN`.
254 #[inline]
255 pub fn aspect_ratio_width(self) -> f64 {
256 // ratio is determined by width / height
257 // https://en.wikipedia.org/wiki/Aspect_ratio_(image)
258 // https://en.wikipedia.org/wiki/Ratio
259 self.width / self.height
260 }
261
262 /// Returns **the inverse** of the aspect ratio of a rectangle with this size.
263 ///
264 /// Aspect ratios are usually defined as the ratio of the width to the height, but
265 /// this method incorrectly returns the ratio of height to width.
266 /// You should generally prefer [`aspect_ratio_width`](Self::aspect_ratio_width).
267 ///
268 /// If the width is `0`, the output will be `sign(self.height) * infinity`. If the width and
269 /// height are both `0`, then the output will be `NaN`.
270 #[deprecated(
271 note = "You should use `aspect_ratio_width` instead, as this method returns a potentially unexpected value.",
272 since = "0.12.0"
273 )]
274 #[inline]
275 // TODO: When we remove this, we should also work out what to do with aspect_ratio_width
276 // The tentatitive plan is to rename `aspect_ratio_width` back to this name
277 pub fn aspect_ratio(self) -> f64 {
278 self.height / self.width
279 }
280
281 /// Convert this `Size` into a [`Rect`] with origin `(0.0, 0.0)`.
282 #[inline(always)]
283 pub const fn to_rect(self) -> Rect {
284 Rect::new(0., 0., self.width, self.height)
285 }
286
287 /// Convert this `Size` into a [`RoundedRect`] with origin `(0.0, 0.0)` and
288 /// the provided corner radius.
289 #[inline]
290 pub fn to_rounded_rect(self, radii: impl Into<RoundedRectRadii>) -> RoundedRect {
291 self.to_rect().to_rounded_rect(radii)
292 }
293
294 /// Is this size [finite]?
295 ///
296 /// [finite]: f64::is_finite
297 #[inline]
298 pub fn is_finite(self) -> bool {
299 self.width.is_finite() && self.height.is_finite()
300 }
301
302 /// Is this size [NaN]?
303 ///
304 /// [NaN]: f64::is_nan
305 #[inline]
306 pub fn is_nan(self) -> bool {
307 self.width.is_nan() || self.height.is_nan()
308 }
309
310 /// Get the member matching the given axis.
311 #[inline]
312 pub fn get_coord(self, axis: Axis) -> f64 {
313 match axis {
314 Axis::Horizontal => self.width,
315 Axis::Vertical => self.height,
316 }
317 }
318
319 /// Get a mutable reference to the member matching the given axis.
320 #[inline]
321 pub fn get_coord_mut(&mut self, axis: Axis) -> &mut f64 {
322 match axis {
323 Axis::Horizontal => &mut self.width,
324 Axis::Vertical => &mut self.height,
325 }
326 }
327
328 /// Set the member matching the given axis to the given value.
329 #[inline]
330 pub fn set_coord(&mut self, axis: Axis, value: f64) {
331 match axis {
332 Axis::Horizontal => self.width = value,
333 Axis::Vertical => self.height = value,
334 }
335 }
336}
337
338impl fmt::Debug for Size {
339 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
340 write!(f, "{:?}W×{:?}H", self.width, self.height)
341 }
342}
343
344impl fmt::Display for Size {
345 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
346 write!(formatter, "(")?;
347 fmt::Display::fmt(&self.width, formatter)?;
348 write!(formatter, "×")?;
349 fmt::Display::fmt(&self.height, formatter)?;
350 write!(formatter, ")")
351 }
352}
353
354impl MulAssign<f64> for Size {
355 #[inline]
356 fn mul_assign(&mut self, other: f64) {
357 *self = Size {
358 width: self.width * other,
359 height: self.height * other,
360 };
361 }
362}
363
364impl Mul<Size> for f64 {
365 type Output = Size;
366
367 #[inline]
368 fn mul(self, other: Size) -> Size {
369 other * self
370 }
371}
372
373impl Mul<f64> for Size {
374 type Output = Size;
375
376 #[inline]
377 fn mul(self, other: f64) -> Size {
378 Size {
379 width: self.width * other,
380 height: self.height * other,
381 }
382 }
383}
384
385impl DivAssign<f64> for Size {
386 #[inline]
387 fn div_assign(&mut self, other: f64) {
388 *self = Size {
389 width: self.width / other,
390 height: self.height / other,
391 };
392 }
393}
394
395impl Div<f64> for Size {
396 type Output = Size;
397
398 #[inline]
399 fn div(self, other: f64) -> Size {
400 Size {
401 width: self.width / other,
402 height: self.height / other,
403 }
404 }
405}
406
407impl Add<Size> for Size {
408 type Output = Size;
409 #[inline]
410 fn add(self, other: Size) -> Size {
411 Size {
412 width: self.width + other.width,
413 height: self.height + other.height,
414 }
415 }
416}
417
418impl AddAssign<Size> for Size {
419 #[inline]
420 fn add_assign(&mut self, other: Size) {
421 *self = *self + other;
422 }
423}
424
425impl Sub<Size> for Size {
426 type Output = Size;
427 #[inline]
428 fn sub(self, other: Size) -> Size {
429 Size {
430 width: self.width - other.width,
431 height: self.height - other.height,
432 }
433 }
434}
435
436impl SubAssign<Size> for Size {
437 #[inline]
438 fn sub_assign(&mut self, other: Size) {
439 *self = *self - other;
440 }
441}
442
443impl From<(f64, f64)> for Size {
444 #[inline(always)]
445 fn from(v: (f64, f64)) -> Size {
446 Size {
447 width: v.0,
448 height: v.1,
449 }
450 }
451}
452
453impl From<Size> for (f64, f64) {
454 #[inline(always)]
455 fn from(v: Size) -> (f64, f64) {
456 (v.width, v.height)
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 #[test]
465 fn display() {
466 let s = Size::new(-0.12345, 9.87654);
467 assert_eq!(format!("{s}"), "(-0.12345×9.87654)");
468
469 let s = Size::new(-0.12345, 9.87654);
470 assert_eq!(format!("{s:+6.2}"), "( -0.12× +9.88)");
471 }
472
473 #[test]
474 #[expect(deprecated, reason = "Testing deprecated function.")]
475 fn aspect_ratio() {
476 let s = Size::new(1.0, 1.0);
477 assert!((s.aspect_ratio() - 1.0).abs() < 1e-6);
478 }
479
480 #[test]
481 fn aspect_ratio_width() {
482 let s = Size::new(1.0, 1.0);
483 assert!((s.aspect_ratio_width() - 1.0).abs() < 1e-6);
484
485 // 3:2 film (mm)
486 let s = Size::new(36.0, 24.0);
487 assert!((s.aspect_ratio_width() - 1.5).abs() < 1e-6);
488 // 4k screen
489 let s = Size::new(3840.0, 2160.0);
490 assert!((s.aspect_ratio_width() - (16. / 9.)).abs() < 1e-6);
491 }
492}