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 /// Create a new `Size` with the provided `width` and `height`.
31 #[inline]
32 pub const fn new(width: f64, height: f64) -> Self {
33 Size { width, height }
34 }
35
36 /// Returns the max of `width` and `height`.
37 ///
38 /// # Examples
39 ///
40 /// ```
41 /// use kurbo::Size;
42 /// let size = Size::new(-10.5, 42.0);
43 /// assert_eq!(size.max_side(), 42.0);
44 /// ```
45 pub fn max_side(self) -> f64 {
46 self.width.max(self.height)
47 }
48
49 /// Returns the min of `width` and `height`.
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// use kurbo::Size;
55 /// let size = Size::new(-10.5, 42.0);
56 /// assert_eq!(size.min_side(), -10.5);
57 /// ```
58 pub fn min_side(self) -> f64 {
59 self.width.min(self.height)
60 }
61
62 /// The area covered by this size.
63 #[inline]
64 pub fn area(self) -> f64 {
65 self.width * self.height
66 }
67
68 /// Whether this size has zero area.
69 ///
70 /// Note: a size with negative area is not considered empty.
71 #[inline]
72 pub fn is_empty(self) -> bool {
73 self.area() == 0.0
74 }
75
76 /// Returns a new size bounded by `min` and `max.`
77 ///
78 /// # Examples
79 ///
80 /// ```
81 /// use kurbo::Size;
82 ///
83 /// let this = Size::new(0., 100.);
84 /// let min = Size::new(10., 10.,);
85 /// let max = Size::new(50., 50.);
86 /// assert_eq!(this.clamp(min, max), Size::new(10., 50.))
87 /// ```
88 pub fn clamp(self, min: Size, max: Size) -> Self {
89 let width = self.width.max(min.width).min(max.width);
90 let height = self.height.max(min.height).min(max.height);
91 Size { width, height }
92 }
93
94 /// Convert this size into a [`Vec2`], with `width` mapped to `x` and `height`
95 /// mapped to `y`.
96 #[inline]
97 pub const fn to_vec2(self) -> Vec2 {
98 Vec2::new(self.width, self.height)
99 }
100
101 /// Returns a new `Size`,
102 /// with `width` and `height` rounded to the nearest integer.
103 ///
104 /// # Examples
105 ///
106 /// ```
107 /// use kurbo::Size;
108 /// let size_pos = Size::new(3.3, 3.6).round();
109 /// assert_eq!(size_pos.width, 3.0);
110 /// assert_eq!(size_pos.height, 4.0);
111 /// let size_neg = Size::new(-3.3, -3.6).round();
112 /// assert_eq!(size_neg.width, -3.0);
113 /// assert_eq!(size_neg.height, -4.0);
114 /// ```
115 #[inline]
116 pub fn round(self) -> Size {
117 Size::new(self.width.round(), self.height.round())
118 }
119
120 /// Returns a new `Size`,
121 /// with `width` and `height` rounded up to the nearest integer,
122 /// unless they are already an integer.
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// use kurbo::Size;
128 /// let size_pos = Size::new(3.3, 3.6).ceil();
129 /// assert_eq!(size_pos.width, 4.0);
130 /// assert_eq!(size_pos.height, 4.0);
131 /// let size_neg = Size::new(-3.3, -3.6).ceil();
132 /// assert_eq!(size_neg.width, -3.0);
133 /// assert_eq!(size_neg.height, -3.0);
134 /// ```
135 #[inline]
136 pub fn ceil(self) -> Size {
137 Size::new(self.width.ceil(), self.height.ceil())
138 }
139
140 /// Returns a new `Size`,
141 /// with `width` and `height` rounded down to the nearest integer,
142 /// unless they are already an integer.
143 ///
144 /// # Examples
145 ///
146 /// ```
147 /// use kurbo::Size;
148 /// let size_pos = Size::new(3.3, 3.6).floor();
149 /// assert_eq!(size_pos.width, 3.0);
150 /// assert_eq!(size_pos.height, 3.0);
151 /// let size_neg = Size::new(-3.3, -3.6).floor();
152 /// assert_eq!(size_neg.width, -4.0);
153 /// assert_eq!(size_neg.height, -4.0);
154 /// ```
155 #[inline]
156 pub fn floor(self) -> Size {
157 Size::new(self.width.floor(), self.height.floor())
158 }
159
160 /// Returns a new `Size`,
161 /// with `width` and `height` rounded away from zero 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).expand();
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).expand();
172 /// assert_eq!(size_neg.width, -4.0);
173 /// assert_eq!(size_neg.height, -4.0);
174 /// ```
175 #[inline]
176 pub fn expand(self) -> Size {
177 Size::new(self.width.expand(), self.height.expand())
178 }
179
180 /// Returns a new `Size`,
181 /// with `width` and `height` rounded down towards zero the nearest integer,
182 /// unless they are already an integer.
183 ///
184 /// # Examples
185 ///
186 /// ```
187 /// use kurbo::Size;
188 /// let size_pos = Size::new(3.3, 3.6).trunc();
189 /// assert_eq!(size_pos.width, 3.0);
190 /// assert_eq!(size_pos.height, 3.0);
191 /// let size_neg = Size::new(-3.3, -3.6).trunc();
192 /// assert_eq!(size_neg.width, -3.0);
193 /// assert_eq!(size_neg.height, -3.0);
194 /// ```
195 #[inline]
196 pub fn trunc(self) -> Size {
197 Size::new(self.width.trunc(), self.height.trunc())
198 }
199
200 /// Returns the aspect ratio of a rectangle with the given size.
201 ///
202 /// If the width is `0`, the output will be `sign(self.height) * infinity`. If The width and
203 /// height are `0`, then the output will be `NaN`.
204 pub fn aspect_ratio(self) -> f64 {
205 self.height / self.width
206 }
207
208 /// Convert this `Size` into a [`Rect`] with origin `(0.0, 0.0)`.
209 #[inline]
210 pub const fn to_rect(self) -> Rect {
211 Rect::new(0., 0., self.width, self.height)
212 }
213
214 /// Convert this `Size` into a [`RoundedRect`] with origin `(0.0, 0.0)` and
215 /// the provided corner radius.
216 #[inline]
217 pub fn to_rounded_rect(self, radii: impl Into<RoundedRectRadii>) -> RoundedRect {
218 self.to_rect().to_rounded_rect(radii)
219 }
220
221 /// Is this size finite?
222 #[inline]
223 pub fn is_finite(self) -> bool {
224 self.width.is_finite() && self.height.is_finite()
225 }
226
227 /// Is this size NaN?
228 #[inline]
229 pub fn is_nan(self) -> bool {
230 self.width.is_nan() || self.height.is_nan()
231 }
232}
233
234impl fmt::Debug for Size {
235 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
236 write!(f, "{:?}{:?}H", self.width, self.height)
237 }
238}
239
240impl fmt::Display for Size {
241 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
242 write!(formatter, "(")?;
243 fmt::Display::fmt(&self.width, f:formatter)?;
244 write!(formatter, "×")?;
245 fmt::Display::fmt(&self.height, f:formatter)?;
246 write!(formatter, ")")
247 }
248}
249
250impl MulAssign<f64> for Size {
251 #[inline]
252 fn mul_assign(&mut self, other: f64) {
253 *self = Size {
254 width: self.width * other,
255 height: self.height * other,
256 };
257 }
258}
259
260impl Mul<Size> for f64 {
261 type Output = Size;
262
263 #[inline]
264 fn mul(self, other: Size) -> Size {
265 other * self
266 }
267}
268
269impl Mul<f64> for Size {
270 type Output = Size;
271
272 #[inline]
273 fn mul(self, other: f64) -> Size {
274 Size {
275 width: self.width * other,
276 height: self.height * other,
277 }
278 }
279}
280
281impl DivAssign<f64> for Size {
282 #[inline]
283 fn div_assign(&mut self, other: f64) {
284 *self = Size {
285 width: self.width / other,
286 height: self.height / other,
287 };
288 }
289}
290
291impl Div<f64> for Size {
292 type Output = Size;
293
294 #[inline]
295 fn div(self, other: f64) -> Size {
296 Size {
297 width: self.width / other,
298 height: self.height / other,
299 }
300 }
301}
302
303impl Add<Size> for Size {
304 type Output = Size;
305 #[inline]
306 fn add(self, other: Size) -> Size {
307 Size {
308 width: self.width + other.width,
309 height: self.height + other.height,
310 }
311 }
312}
313
314impl AddAssign<Size> for Size {
315 #[inline]
316 fn add_assign(&mut self, other: Size) {
317 *self = *self + other;
318 }
319}
320
321impl Sub<Size> for Size {
322 type Output = Size;
323 #[inline]
324 fn sub(self, other: Size) -> Size {
325 Size {
326 width: self.width - other.width,
327 height: self.height - other.height,
328 }
329 }
330}
331
332impl SubAssign<Size> for Size {
333 #[inline]
334 fn sub_assign(&mut self, other: Size) {
335 *self = *self - other;
336 }
337}
338
339impl From<(f64, f64)> for Size {
340 #[inline]
341 fn from(v: (f64, f64)) -> Size {
342 Size {
343 width: v.0,
344 height: v.1,
345 }
346 }
347}
348
349impl From<Size> for (f64, f64) {
350 #[inline]
351 fn from(v: Size) -> (f64, f64) {
352 (v.width, v.height)
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn display() {
362 let s = Size::new(-0.12345, 9.87654);
363 assert_eq!(format!("{s}"), "(-0.12345×9.87654)");
364
365 let s = Size::new(-0.12345, 9.87654);
366 assert_eq!(format!("{s:+6.2}"), "( -0.12× +9.88)");
367 }
368
369 #[test]
370 fn aspect_ratio() {
371 let s = Size::new(1.0, 1.0);
372 assert!((s.aspect_ratio() - 1.0).abs() < 1e-6);
373 }
374}
375