1use std::fmt;
4use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
5use std::str::FromStr;
6
7use crate::utilities::math_utilities::{self, MathUtilities};
8use crate::utilities::span_helpers::InvariantF64;
9use crate::utilities::{FormatError, SpanStringTokenizer};
10use crate::{Thickness, Vector};
11
12#[derive(Clone, Copy, Debug, PartialEq, Default)]
14pub struct Size {
15 pub width: f64,
17 pub height: f64,
19}
20
21impl Size {
22 pub const INFINITY: Size = Size::new(f64::INFINITY, f64::INFINITY);
24
25 #[inline]
27 pub const fn new(width: f64, height: f64) -> Self {
28 Self { width, height }
29 }
30
31 #[inline]
33 pub fn aspect_ratio(&self) -> f64 {
34 self.width / self.height
35 }
36
37 pub fn parse(s: &str) -> Result<Self, FormatError> {
39 SpanStringTokenizer::with_message(s, "Invalid Size.")
40 .scope(|t| Ok(Size::new(t.read_double()?, t.read_double()?)))
41 }
42
43 #[inline]
46 pub fn constrain(&self, constraint: Size) -> Size {
47 Size::new(
48 math_utilities::min(self.width, constraint.width),
49 math_utilities::min(self.height, constraint.height),
50 )
51 }
52
53 #[inline]
55 pub fn deflate(&self, thickness: Thickness) -> Size {
56 let mut width = self.width - thickness.left - thickness.right;
57 if width < 0.0 {
58 width = 0.0;
59 }
60
61 let mut height = self.height - thickness.top - thickness.bottom;
62 if height < 0.0 {
63 height = 0.0;
64 }
65
66 Size::new(width, height)
67 }
68
69 #[inline]
72 pub fn equals(&self, other: Size) -> bool {
73 self.width == other.width && self.height == other.height
74 }
75
76 #[inline]
79 pub fn nearly_equals(&self, other: Size) -> bool {
80 MathUtilities::are_close(self.width, other.width)
81 && MathUtilities::are_close(self.height, other.height)
82 }
83
84 #[inline]
86 pub fn inflate(&self, thickness: Thickness) -> Size {
87 Size::new(
88 self.width + thickness.left + thickness.right,
89 self.height + thickness.top + thickness.bottom,
90 )
91 }
92
93 #[inline]
95 pub const fn with_width(&self, width: f64) -> Size {
96 Size::new(width, self.height)
97 }
98
99 #[inline]
101 pub const fn with_height(&self, height: f64) -> Size {
102 Size::new(self.width, height)
103 }
104
105 #[inline]
107 pub const fn deconstruct(&self) -> (f64, f64) {
108 (self.width, self.height)
109 }
110}
111
112impl From<Size> for (f64, f64) {
113 #[inline]
114 fn from(s: Size) -> Self {
115 (s.width, s.height)
116 }
117}
118
119impl Mul<Vector> for Size {
121 type Output = Size;
122 #[inline]
123 fn mul(self, scale: Vector) -> Size {
124 Size::new(self.width * scale.x, self.height * scale.y)
125 }
126}
127
128impl Div<Vector> for Size {
130 type Output = Size;
131 #[inline]
132 fn div(self, scale: Vector) -> Size {
133 Size::new(self.width / scale.x, self.height / scale.y)
134 }
135}
136
137impl Div for Size {
139 type Output = Vector;
140 #[inline]
141 fn div(self, right: Size) -> Vector {
142 Vector::new(self.width / right.width, self.height / right.height)
143 }
144}
145
146impl Mul<f64> for Size {
148 type Output = Size;
149 #[inline]
150 fn mul(self, scale: f64) -> Size {
151 Size::new(self.width * scale, self.height * scale)
152 }
153}
154
155impl Div<f64> for Size {
157 type Output = Size;
158 #[inline]
159 fn div(self, scale: f64) -> Size {
160 Size::new(self.width / scale, self.height / scale)
161 }
162}
163
164impl Add for Size {
165 type Output = Size;
166 #[inline]
167 fn add(self, to_add: Size) -> Size {
168 Size::new(self.width + to_add.width, self.height + to_add.height)
169 }
170}
171
172impl Sub for Size {
173 type Output = Size;
174 #[inline]
175 fn sub(self, to_subtract: Size) -> Size {
176 Size::new(
177 self.width - to_subtract.width,
178 self.height - to_subtract.height,
179 )
180 }
181}
182
183impl AddAssign for Size {
184 #[inline]
185 fn add_assign(&mut self, rhs: Size) {
186 *self = *self + rhs;
187 }
188}
189
190impl SubAssign for Size {
191 #[inline]
192 fn sub_assign(&mut self, rhs: Size) {
193 *self = *self - rhs;
194 }
195}
196
197impl MulAssign<f64> for Size {
198 #[inline]
199 fn mul_assign(&mut self, scale: f64) {
200 *self = *self * scale;
201 }
202}
203
204impl DivAssign<f64> for Size {
205 #[inline]
206 fn div_assign(&mut self, scale: f64) {
207 *self = *self / scale;
208 }
209}
210
211impl MulAssign<Vector> for Size {
212 #[inline]
213 fn mul_assign(&mut self, scale: Vector) {
214 *self = *self * scale;
215 }
216}
217
218impl DivAssign<Vector> for Size {
219 #[inline]
220 fn div_assign(&mut self, scale: Vector) {
221 *self = *self / scale;
222 }
223}
224
225impl FromStr for Size {
226 type Err = FormatError;
227 #[inline]
228 fn from_str(s: &str) -> Result<Self, FormatError> {
229 Size::parse(s)
230 }
231}
232
233impl fmt::Display for Size {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 write!(
236 f,
237 "{}, {}",
238 InvariantF64(self.width),
239 InvariantF64(self.height)
240 )
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn should_produce_correct_aspect_ratio() {
250 let result = Size::new(3.0, 2.0).aspect_ratio();
251
252 assert_eq!(1.5, result);
253 }
254
255 #[test]
256 fn dividing_should_produce_scaling_factor() {
257 let result = Size::new(15.0, 10.0) / Size::new(5.0, 5.0);
258
259 assert_eq!(Vector::new(3.0, 2.0), result);
260 }
261
262 #[test]
265 fn parse_display_inflate_deflate_constrain() {
266 assert_eq!(Size::parse("10, 20").unwrap(), Size::new(10.0, 20.0));
267 assert_eq!(Size::parse("10").unwrap_err().message(), "Invalid Size.");
268 assert_eq!(Size::INFINITY.to_string(), "Infinity, Infinity");
269 let t = Thickness::new(1.0, 2.0, 3.0, 4.0);
270 assert_eq!(Size::new(10.0, 10.0).inflate(t), Size::new(14.0, 16.0));
271 assert_eq!(Size::new(10.0, 10.0).deflate(t), Size::new(6.0, 4.0));
272 assert_eq!(Size::new(1.0, 1.0).deflate(t), Size::new(0.0, 0.0));
273 assert_eq!(
274 Size::new(10.0, 30.0).constrain(Size::new(20.0, 20.0)),
275 Size::new(10.0, 20.0)
276 );
277 assert!(Size::new(f64::NAN, 1.0)
278 .constrain(Size::new(1.0, 1.0))
279 .width
280 .is_nan());
281 }
282}