kurbo/
insets.rs

1// Copyright 2019 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A description of the distances between the edges of two rectangles.
5
6use core::ops::{Add, Div, Mul, Neg, Sub};
7
8use crate::{Rect, Size};
9
10/// Insets from the edges of a rectangle.
11///
12///
13/// The inset value for each edge can be thought of as a delta computed from
14/// the center of the rect to that edge. For instance, with an inset of `2.0` on
15/// the x-axis, a rectangle with the origin `(0.0, 0.0)` with that inset added
16/// will have the new origin at `(-2.0, 0.0)`.
17///
18/// Put alternatively, a positive inset represents increased distance from center,
19/// and a negative inset represents decreased distance from center.
20///
21/// # Examples
22///
23/// Positive insets added to a [`Rect`] produce a larger [`Rect`]:
24/// ```
25/// # use kurbo::{Insets, Rect};
26/// let rect = Rect::from_origin_size((0., 0.,), (10., 10.,));
27/// let insets = Insets::uniform_xy(3., 0.,);
28///
29/// let inset_rect = rect + insets;
30/// assert_eq!(inset_rect.width(), 16.0, "10.0 + 3.0 × 2");
31/// assert_eq!(inset_rect.x0, -3.0);
32/// ```
33///
34/// Negative insets added to a [`Rect`] produce a smaller [`Rect`]:
35///
36/// ```
37/// # use kurbo::{Insets, Rect};
38/// let rect = Rect::from_origin_size((0., 0.,), (10., 10.,));
39/// let insets = Insets::uniform_xy(-3., 0.,);
40///
41/// let inset_rect = rect + insets;
42/// assert_eq!(inset_rect.width(), 4.0, "10.0 - 3.0 × 2");
43/// assert_eq!(inset_rect.x0, 3.0);
44/// ```
45///
46/// [`Insets`] operate on the absolute rectangle [`Rect::abs`], and so ignore
47/// existing negative widths and heights.
48///
49/// ```
50/// # use kurbo::{Insets, Rect};
51/// let rect = Rect::new(7., 11., 0., 0.,);
52/// let insets = Insets::uniform_xy(0., 1.,);
53///
54/// assert_eq!(rect.width(), -7.0);
55///
56/// let inset_rect = rect + insets;
57/// assert_eq!(inset_rect.width(), 7.0);
58/// assert_eq!(inset_rect.x0, 0.0);
59/// assert_eq!(inset_rect.height(), 13.0);
60/// ```
61///
62/// The width and height of an inset operation can still be negative if the
63/// [`Insets`]' dimensions are greater than the dimensions of the original [`Rect`].
64///
65/// ```
66/// # use kurbo::{Insets, Rect};
67/// let rect = Rect::new(0., 0., 3., 5.);
68/// let insets = Insets::uniform_xy(0., 7.,);
69///
70/// let inset_rect = rect - insets;
71/// assert_eq!(inset_rect.height(), -9., "5 - 7 × 2")
72/// ```
73///
74/// `Rect - Rect = Insets`:
75///
76///
77/// ```
78/// # use kurbo::{Insets, Rect};
79/// let rect = Rect::new(0., 0., 5., 11.);
80/// let insets = Insets::uniform_xy(1., 7.,);
81///
82/// let inset_rect = rect + insets;
83/// let insets2 = inset_rect - rect;
84///
85/// assert_eq!(insets2.x0, insets.x0);
86/// assert_eq!(insets2.y1, insets.y1);
87/// assert_eq!(insets2.x_value(), insets.x_value());
88/// assert_eq!(insets2.y_value(), insets.y_value());
89/// ```
90#[derive(Clone, Copy, Default, Debug, PartialEq)]
91#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93pub struct Insets {
94    /// The minimum x coordinate (left edge).
95    pub x0: f64,
96    /// The minimum y coordinate (top edge in y-down spaces).
97    pub y0: f64,
98    /// The maximum x coordinate (right edge).
99    pub x1: f64,
100    /// The maximum y coordinate (bottom edge in y-down spaces).
101    pub y1: f64,
102}
103
104impl Insets {
105    /// Zeroed insets.
106    pub const ZERO: Insets = Insets::uniform(0.);
107
108    /// New uniform insets.
109    #[inline(always)]
110    pub const fn uniform(d: f64) -> Insets {
111        Insets {
112            x0: d,
113            y0: d,
114            x1: d,
115            y1: d,
116        }
117    }
118
119    /// New insets with uniform values along each axis.
120    #[inline(always)]
121    pub const fn uniform_xy(x: f64, y: f64) -> Insets {
122        Insets {
123            x0: x,
124            y0: y,
125            x1: x,
126            y1: y,
127        }
128    }
129
130    /// New insets. The ordering of the arguments is "left, top, right, bottom",
131    /// assuming a y-down coordinate space.
132    #[inline(always)]
133    pub const fn new(x0: f64, y0: f64, x1: f64, y1: f64) -> Insets {
134        Insets { x0, y0, x1, y1 }
135    }
136
137    /// The total delta on the x-axis represented by these insets.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use kurbo::Insets;
143    ///
144    /// let insets = Insets::uniform_xy(3., 8.);
145    /// assert_eq!(insets.x_value(), 6.);
146    ///
147    /// let insets = Insets::new(5., 0., -12., 0.,);
148    /// assert_eq!(insets.x_value(), -7.);
149    /// ```
150    #[inline]
151    pub fn x_value(self) -> f64 {
152        self.x0 + self.x1
153    }
154
155    /// The total delta on the y-axis represented by these insets.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// use kurbo::Insets;
161    ///
162    /// let insets = Insets::uniform_xy(3., 7.);
163    /// assert_eq!(insets.y_value(), 14.);
164    ///
165    /// let insets = Insets::new(5., 10., -12., 4.,);
166    /// assert_eq!(insets.y_value(), 14.);
167    /// ```
168    #[inline]
169    pub fn y_value(self) -> f64 {
170        self.y0 + self.y1
171    }
172
173    /// Returns the total delta represented by these insets as a [`Size`].
174    ///
175    /// This is equivalent to creating a [`Size`] from the values returned by
176    /// [`x_value`] and [`y_value`].
177    ///
178    /// This function may return a size with negative values.
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// use kurbo::{Insets, Size};
184    ///
185    /// let insets = Insets::new(11.1, -43.3, 3.333, -0.0);
186    /// assert_eq!(insets.size(), Size::new(insets.x_value(), insets.y_value()));
187    /// ```
188    ///
189    /// [`x_value`]: Insets::x_value
190    /// [`y_value`]: Insets::y_value
191    pub fn size(self) -> Size {
192        Size::new(self.x_value(), self.y_value())
193    }
194
195    /// Return `true` iff all values are nonnegative.
196    pub fn are_nonnegative(self) -> bool {
197        let Insets { x0, y0, x1, y1 } = self;
198        x0 >= 0.0 && y0 >= 0.0 && x1 >= 0.0 && y1 >= 0.0
199    }
200
201    /// Return new `Insets` with all negative values replaced with `0.0`.
202    ///
203    /// This is provided as a convenience for applications where negative insets
204    /// are not meaningful.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use kurbo::Insets;
210    ///
211    /// let insets = Insets::new(-10., 3., -0.2, 4.);
212    /// let nonnegative = insets.nonnegative();
213    /// assert_eq!(nonnegative.x_value(), 0.0);
214    /// assert_eq!(nonnegative.y_value(), 7.0);
215    /// ```
216    pub fn nonnegative(self) -> Insets {
217        let Insets { x0, y0, x1, y1 } = self;
218        Insets {
219            x0: x0.max(0.0),
220            y0: y0.max(0.0),
221            x1: x1.max(0.0),
222            y1: y1.max(0.0),
223        }
224    }
225
226    /// Are these insets finite?
227    #[inline]
228    pub fn is_finite(&self) -> bool {
229        self.x0.is_finite() && self.y0.is_finite() && self.x1.is_finite() && self.y1.is_finite()
230    }
231
232    /// Are these insets NaN?
233    #[inline]
234    pub fn is_nan(&self) -> bool {
235        self.x0.is_nan() || self.y0.is_nan() || self.x1.is_nan() || self.y1.is_nan()
236    }
237}
238
239impl Neg for Insets {
240    type Output = Insets;
241
242    #[inline]
243    fn neg(self) -> Insets {
244        Insets::new(-self.x0, -self.y0, -self.x1, -self.y1)
245    }
246}
247
248impl Add<Rect> for Insets {
249    type Output = Rect;
250
251    #[inline]
252    #[allow(clippy::suspicious_arithmetic_impl)]
253    fn add(self, other: Rect) -> Rect {
254        let other = other.abs();
255        Rect::new(
256            other.x0 - self.x0,
257            other.y0 - self.y0,
258            other.x1 + self.x1,
259            other.y1 + self.y1,
260        )
261    }
262}
263
264impl Add<Insets> for Rect {
265    type Output = Rect;
266
267    #[inline]
268    fn add(self, other: Insets) -> Rect {
269        other + self
270    }
271}
272
273impl Sub<Rect> for Insets {
274    type Output = Rect;
275
276    #[inline]
277    fn sub(self, other: Rect) -> Rect {
278        other + -self
279    }
280}
281
282impl Mul<f64> for Insets {
283    type Output = Insets;
284
285    fn mul(self, rhs: f64) -> Self::Output {
286        Self {
287            x0: self.x0 * rhs,
288            y0: self.y0 * rhs,
289            x1: self.x1 * rhs,
290            y1: self.y1 * rhs,
291        }
292    }
293}
294
295impl Div<f64> for Insets {
296    type Output = Insets;
297
298    fn div(self, rhs: f64) -> Self::Output {
299        Self {
300            x0: self.x0 / rhs,
301            y0: self.y0 / rhs,
302            x1: self.x1 / rhs,
303            y1: self.y1 / rhs,
304        }
305    }
306}
307
308impl Sub<Insets> for Rect {
309    type Output = Rect;
310
311    #[inline]
312    fn sub(self, other: Insets) -> Rect {
313        other - self
314    }
315}
316
317impl From<f64> for Insets {
318    #[inline(always)]
319    fn from(src: f64) -> Insets {
320        Insets::uniform(src)
321    }
322}
323
324impl From<(f64, f64)> for Insets {
325    #[inline(always)]
326    fn from(src: (f64, f64)) -> Insets {
327        Insets::uniform_xy(src.0, src.1)
328    }
329}
330
331impl From<(f64, f64, f64, f64)> for Insets {
332    #[inline(always)]
333    fn from(src: (f64, f64, f64, f64)) -> Insets {
334        Insets::new(src.0, src.1, src.2, src.3)
335    }
336}