1 | // Copyright 2019 the Kurbo Authors
|
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
|
3 |
|
4 | //! A 2D size.
|
5 |
|
6 | use core::fmt;
|
7 | use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
8 |
|
9 | use crate::common::FloatExt;
|
10 | use crate::{Rect, RoundedRect, RoundedRectRadii, Vec2};
|
11 |
|
12 | #[cfg (not(feature = "std" ))]
|
13 | use 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))]
|
19 | pub struct Size {
|
20 | /// The width.
|
21 | pub width: f64,
|
22 | /// The height.
|
23 | pub height: f64,
|
24 | }
|
25 |
|
26 | impl 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 |
|
234 | impl fmt::Debug for Size {
|
235 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
236 | write!(f, " {:?}W× {:?}H" , self.width, self.height)
|
237 | }
|
238 | }
|
239 |
|
240 | impl 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 |
|
250 | impl 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 |
|
260 | impl Mul<Size> for f64 {
|
261 | type Output = Size;
|
262 |
|
263 | #[inline ]
|
264 | fn mul(self, other: Size) -> Size {
|
265 | other * self
|
266 | }
|
267 | }
|
268 |
|
269 | impl 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 |
|
281 | impl 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 |
|
291 | impl 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 |
|
303 | impl 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 |
|
314 | impl AddAssign<Size> for Size {
|
315 | #[inline ]
|
316 | fn add_assign(&mut self, other: Size) {
|
317 | *self = *self + other;
|
318 | }
|
319 | }
|
320 |
|
321 | impl 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 |
|
332 | impl SubAssign<Size> for Size {
|
333 | #[inline ]
|
334 | fn sub_assign(&mut self, other: Size) {
|
335 | *self = *self - other;
|
336 | }
|
337 | }
|
338 |
|
339 | impl 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 |
|
349 | impl 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)]
|
357 | mod 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 | |