//! Types for rectangles. use crate::coord_units::CoordUnits; use crate::transform::Transform; #[allow(clippy::module_inception)] mod rect { use crate::float_eq_cairo::ApproxEqCairo; use core::ops::{Add, Range, Sub}; use float_cmp::approx_eq; use num_traits::Zero; // Use our own min() and max() that are acceptable for floating point fn min(x: T, y: T) -> T { if x <= y { x } else { y } } fn max(x: T, y: T) -> T { if x >= y { x } else { y } } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub struct Rect { pub x0: T, pub y0: T, pub x1: T, pub y1: T, } impl Rect { #[inline] pub fn new(x0: T, y0: T, x1: T, y1: T) -> Self { Self { x0, y0, x1, y1 } } } impl Rect where T: Copy + PartialOrd + PartialEq + Add + Sub + Zero, { #[inline] pub fn from_size(w: T, h: T) -> Self { Self { x0: Zero::zero(), y0: Zero::zero(), x1: w, y1: h, } } #[inline] pub fn width(&self) -> T { self.x1 - self.x0 } #[inline] pub fn height(&self) -> T { self.y1 - self.y0 } #[inline] pub fn size(&self) -> (T, T) { (self.width(), self.height()) } #[inline] pub fn x_range(&self) -> Range { self.x0..self.x1 } #[inline] pub fn y_range(&self) -> Range { self.y0..self.y1 } #[inline] pub fn contains(self, x: T, y: T) -> bool { x >= self.x0 && x < self.x1 && y >= self.y0 && y < self.y1 } #[inline] pub fn translate(&self, by: (T, T)) -> Self { Self { x0: self.x0 + by.0, y0: self.y0 + by.1, x1: self.x1 + by.0, y1: self.y1 + by.1, } } #[inline] pub fn intersection(&self, rect: &Self) -> Option { let (x0, y0, x1, y1) = ( max(self.x0, rect.x0), max(self.y0, rect.y0), min(self.x1, rect.x1), min(self.y1, rect.y1), ); if x1 > x0 && y1 > y0 { Some(Self { x0, y0, x1, y1 }) } else { None } } #[inline] pub fn union(&self, rect: &Self) -> Self { Self { x0: min(self.x0, rect.x0), y0: min(self.y0, rect.y0), x1: max(self.x1, rect.x1), y1: max(self.y1, rect.y1), } } } impl Rect { #[inline] pub fn is_empty(&self) -> bool { // Give an explicit type to the right hand side of the ==, since sometimes // type inference fails to figure it out. I have no idea why. self.width() == ::zero() || self.height() == ::zero() } #[inline] pub fn scale(self, x: f64, y: f64) -> Self { Self { x0: (f64::from(self.x0) * x).floor() as i32, y0: (f64::from(self.y0) * y).floor() as i32, x1: (f64::from(self.x1) * x).ceil() as i32, y1: (f64::from(self.y1) * y).ceil() as i32, } } } impl Rect { #[inline] pub fn is_empty(&self) -> bool { self.width().approx_eq_cairo(0.0) || self.height().approx_eq_cairo(0.0) } pub fn approx_eq(&self, other: &Self) -> bool { // FIXME: this is super fishy; shouldn't we be using 2x the epsilon against the width/height // instead of the raw coordinates? approx_eq!(f64, self.x0, other.x0, epsilon = 0.0001) && approx_eq!(f64, self.y0, other.y0, epsilon = 0.0001) && approx_eq!(f64, self.x1, other.x1, epsilon = 0.0001) && approx_eq!(f64, self.y1, other.y1, epsilon = 0.00012) } } } pub type Rect = rect::Rect; impl From for IRect { #[inline] fn from(r: Rect) -> Self { Self { x0: r.x0.floor() as i32, y0: r.y0.floor() as i32, x1: r.x1.ceil() as i32, y1: r.y1.ceil() as i32, } } } impl From for Rect { #[inline] fn from(r: cairo::Rectangle) -> Self { Self { x0: r.x(), y0: r.y(), x1: r.x() + r.width(), y1: r.y() + r.height(), } } } impl From for cairo::Rectangle { #[inline] fn from(r: Rect) -> Self { Self::new(r.x0, r.y0, r.x1 - r.x0, r.y1 - r.y0) } } /// Creates a transform to map to a rectangle. /// /// The rectangle is an `Option` to indicate the possibility that there is no /// bounding box from where the rectangle could be obtained. /// /// This depends on a `CoordUnits` parameter. When this is /// `CoordUnits::ObjectBoundingBox`, the bounding box must not be empty, since the calling /// code would then not have a usable size to work with. In that case, if the bbox is /// empty, this function returns `Err(())`. /// /// Usually calling code can simply ignore the action it was about to take if this /// function returns an error. pub fn rect_to_transform(rect: &Option, units: CoordUnits) -> Result { match units { CoordUnits::UserSpaceOnUse => Ok(Transform::identity()), CoordUnits::ObjectBoundingBox => { if rect.as_ref().is_none_or(|r| r.is_empty()) { Err(()) } else { let r = rect.as_ref().unwrap(); let t = Transform::new_unchecked(r.width(), 0.0, 0.0, r.height(), r.x0, r.y0); if t.is_invertible() { Ok(t) } else { Err(()) } } } } } pub type IRect = rect::Rect; impl From for Rect { #[inline] fn from(r: IRect) -> Self { Self { x0: f64::from(r.x0), y0: f64::from(r.y0), x1: f64::from(r.x1), y1: f64::from(r.y1), } } } impl From for cairo::Rectangle { #[inline] fn from(r: IRect) -> Self { Self::new( f64::from(r.x0), f64::from(r.y0), f64::from(r.x1 - r.x0), f64::from(r.y1 - r.y0), ) } }