Lines
89.29 %
Functions
40.37 %
Branches
73.44 %
//! 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<T: PartialOrd>(x: T, y: T) -> T {
if x <= y {
x
} else {
y
}
fn max<T: PartialOrd>(x: T, y: T) -> T {
if x >= y {
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect<T> {
pub x0: T,
pub y0: T,
pub x1: T,
pub y1: T,
impl<T> Rect<T> {
#[inline]
pub fn new(x0: T, y0: T, x1: T, y1: T) -> Self {
Self { x0, y0, x1, y1 }
impl<T> Rect<T>
where
T: Copy + PartialOrd + PartialEq + Add<T, Output = T> + Sub<T, Output = T> + Zero,
{
pub fn from_size(w: T, h: T) -> Self {
Self {
x0: Zero::zero(),
y0: Zero::zero(),
x1: w,
y1: h,
pub fn width(&self) -> T {
self.x1 - self.x0
pub fn height(&self) -> T {
self.y1 - self.y0
pub fn size(&self) -> (T, T) {
(self.width(), self.height())
pub fn x_range(&self) -> Range<T> {
self.x0..self.x1
pub fn y_range(&self) -> Range<T> {
self.y0..self.y1
pub fn contains(self, x: T, y: T) -> bool {
x >= self.x0 && x < self.x1 && y >= self.y0 && y < self.y1
pub fn translate(&self, by: (T, T)) -> Self {
x0: self.x0 + by.0,
y0: self.y0 + by.1,
x1: self.x1 + by.0,
y1: self.y1 + by.1,
pub fn intersection(&self, rect: &Self) -> Option<Self> {
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 })
None
pub fn union(&self, rect: &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<i32> {
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() == <i32 as Zero>::zero() || self.height() == <i32 as Zero>::zero()
pub fn scale(self, x: f64, y: f64) -> 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<f64> {
self.width().approx_eq_cairo(0.0) || self.height().approx_eq_cairo(0.0)
x0: self.x0 * x,
y0: self.y0 * y,
x1: self.x1 * x,
y1: self.y1 * y,
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<f64>;
impl From<Rect> for IRect {
fn from(r: Rect) -> 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<cairo::Rectangle> for Rect {
fn from(r: cairo::Rectangle) -> Self {
x0: r.x(),
y0: r.y(),
x1: r.x() + r.width(),
y1: r.y() + r.height(),
impl From<Rect> for cairo::Rectangle {
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<Rect>` 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<Rect>, units: CoordUnits) -> Result<Transform, ()> {
match units {
CoordUnits::UserSpaceOnUse => Ok(Transform::identity()),
CoordUnits::ObjectBoundingBox => {
if rect.as_ref().map_or(true, |r| r.is_empty()) {
Err(())
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)
pub type IRect = rect::Rect<i32>;
impl From<IRect> for Rect {
fn from(r: IRect) -> Self {
x0: f64::from(r.x0),
y0: f64::from(r.y0),
x1: f64::from(r.x1),
y1: f64::from(r.y1),
impl From<cairo::Rectangle> for IRect {
x0: r.x().floor() as i32,
y0: r.y().floor() as i32,
x1: (r.x() + r.width()).ceil() as i32,
y1: (r.y() + r.height()).ceil() as i32,
impl From<IRect> for cairo::Rectangle {
Self::new(
f64::from(r.x0),
f64::from(r.y0),
f64::from(r.x1 - r.x0),
f64::from(r.y1 - r.y0),
)