use core::convert::TryFrom;
use crate::{FiniteF32, IntSize, LengthU32, PathBuilder, Point, SaturateRound, Size, Transform};
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use crate::NoStdFloat;
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct IntRect {
x: i32,
y: i32,
width: LengthU32,
height: LengthU32,
}
impl IntRect {
pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Option<Self> {
x.checked_add(i32::try_from(width).ok()?)?;
y.checked_add(i32::try_from(height).ok()?)?;
Some(IntRect {
x,
y,
width: LengthU32::new(width)?,
height: LengthU32::new(height)?,
})
}
pub fn from_ltrb(left: i32, top: i32, right: i32, bottom: i32) -> Option<Self> {
let width = u32::try_from(right.checked_sub(left)?).ok()?;
let height = u32::try_from(bottom.checked_sub(top)?).ok()?;
IntRect::from_xywh(left, top, width, height)
}
pub fn x(&self) -> i32 {
self.x
}
pub fn y(&self) -> i32 {
self.y
}
pub fn width(&self) -> u32 {
self.width.get()
}
pub fn height(&self) -> u32 {
self.height.get()
}
pub fn left(&self) -> i32 {
self.x
}
pub fn top(&self) -> i32 {
self.y
}
pub fn right(&self) -> i32 {
self.x + self.width.get() as i32
}
pub fn bottom(&self) -> i32 {
self.y + self.height.get() as i32
}
pub fn size(&self) -> IntSize {
IntSize::from_wh_safe(self.width, self.height)
}
pub fn contains(&self, other: &Self) -> bool {
self.x <= other.x
&& self.y <= other.y
&& self.right() >= other.right()
&& self.bottom() >= other.bottom()
}
pub fn intersect(&self, other: &Self) -> Option<Self> {
let left = self.x.max(other.x);
let top = self.y.max(other.y);
let right = self.right().min(other.right());
let bottom = self.bottom().min(other.bottom());
let w = u32::try_from(right.checked_sub(left)?).ok()?;
let h = u32::try_from(bottom.checked_sub(top)?).ok()?;
IntRect::from_xywh(left, top, w, h)
}
pub fn inset(&self, dx: i32, dy: i32) -> Option<Self> {
IntRect::from_ltrb(
self.left() + dx,
self.top() + dy,
self.right() - dx,
self.bottom() - dy,
)
}
pub fn make_outset(&self, dx: i32, dy: i32) -> Option<Self> {
IntRect::from_ltrb(
self.left().saturating_sub(dx),
self.top().saturating_sub(dy),
self.right().saturating_add(dx),
self.bottom().saturating_add(dy),
)
}
pub fn translate(&self, tx: i32, ty: i32) -> Option<Self> {
IntRect::from_xywh(self.x() + tx, self.y() + ty, self.width(), self.height())
}
pub fn translate_to(&self, x: i32, y: i32) -> Option<Self> {
IntRect::from_xywh(x, y, self.width(), self.height())
}
pub fn to_rect(&self) -> Rect {
Rect::from_ltrb(
self.x as f32,
self.y as f32,
self.x as f32 + self.width.get() as f32,
self.y as f32 + self.height.get() as f32,
)
.unwrap()
}
}
#[cfg(test)]
mod int_rect_tests {
use super::*;
#[test]
fn tests() {
assert_eq!(IntRect::from_xywh(0, 0, 0, 0), None);
assert_eq!(IntRect::from_xywh(0, 0, 1, 0), None);
assert_eq!(IntRect::from_xywh(0, 0, 0, 1), None);
assert_eq!(IntRect::from_xywh(0, 0, u32::MAX, u32::MAX), None);
assert_eq!(IntRect::from_xywh(0, 0, 1, u32::MAX), None);
assert_eq!(IntRect::from_xywh(0, 0, u32::MAX, 1), None);
assert_eq!(IntRect::from_xywh(i32::MAX, 0, 1, 1), None);
assert_eq!(IntRect::from_xywh(0, i32::MAX, 1, 1), None);
{
let r1 = IntRect::from_xywh(1, 2, 3, 4).unwrap();
let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
assert_eq!(r1.intersect(&r2), None);
}
{
let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 13, 14));
}
{
let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
let r2 = IntRect::from_xywh(11, 12, 50, 60).unwrap();
assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 20, 30));
}
}
}
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq)]
pub struct Rect {
left: FiniteF32,
top: FiniteF32,
right: FiniteF32,
bottom: FiniteF32,
}
impl core::fmt::Debug for Rect {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Rect")
.field("left", &self.left.get())
.field("top", &self.top.get())
.field("right", &self.right.get())
.field("bottom", &self.bottom.get())
.finish()
}
}
impl Rect {
pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> {
let left = FiniteF32::new(left)?;
let top = FiniteF32::new(top)?;
let right = FiniteF32::new(right)?;
let bottom = FiniteF32::new(bottom)?;
if left.get() <= right.get() && top.get() <= bottom.get() {
checked_f32_sub(right.get(), left.get())?;
checked_f32_sub(bottom.get(), top.get())?;
Some(Rect {
left,
top,
right,
bottom,
})
} else {
None
}
}
pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
Rect::from_ltrb(x, y, w + x, h + y)
}
pub fn left(&self) -> f32 {
self.left.get()
}
pub fn top(&self) -> f32 {
self.top.get()
}
pub fn right(&self) -> f32 {
self.right.get()
}
pub fn bottom(&self) -> f32 {
self.bottom.get()
}
pub fn x(&self) -> f32 {
self.left.get()
}
pub fn y(&self) -> f32 {
self.top.get()
}
#[inline]
pub fn width(&self) -> f32 {
self.right.get() - self.left.get()
}
#[inline]
pub fn height(&self) -> f32 {
self.bottom.get() - self.top.get()
}
pub fn round(&self) -> Option<IntRect> {
IntRect::from_xywh(
i32::saturate_round(self.x()),
i32::saturate_round(self.y()),
core::cmp::max(1, i32::saturate_round(self.width()) as u32),
core::cmp::max(1, i32::saturate_round(self.height()) as u32),
)
}
pub fn round_out(&self) -> Option<IntRect> {
IntRect::from_xywh(
i32::saturate_floor(self.x()),
i32::saturate_floor(self.y()),
core::cmp::max(1, i32::saturate_ceil(self.width()) as u32),
core::cmp::max(1, i32::saturate_ceil(self.height()) as u32),
)
}
pub fn intersect(&self, other: &Self) -> Option<Self> {
let left = self.x().max(other.x());
let top = self.y().max(other.y());
let right = self.right().min(other.right());
let bottom = self.bottom().min(other.bottom());
Rect::from_ltrb(left, top, right, bottom)
}
pub fn from_points(points: &[Point]) -> Option<Self> {
use crate::f32x4_t::f32x4;
if points.is_empty() {
return None;
}
let mut offset = 0;
let mut min;
let mut max;
if points.len() & 1 != 0 {
let pt = points[0];
min = f32x4([pt.x, pt.y, pt.x, pt.y]);
max = min;
offset += 1;
} else {
let pt0 = points[0];
let pt1 = points[1];
min = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
max = min;
offset += 2;
}
let mut accum = f32x4::default();
while offset != points.len() {
let pt0 = points[offset + 0];
let pt1 = points[offset + 1];
let xy = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
accum *= xy;
min = min.min(xy);
max = max.max(xy);
offset += 2;
}
let all_finite = accum * f32x4::default() == f32x4::default();
let min: [f32; 4] = min.0;
let max: [f32; 4] = max.0;
if all_finite {
Rect::from_ltrb(
min[0].min(min[2]),
min[1].min(min[3]),
max[0].max(max[2]),
max[1].max(max[3]),
)
} else {
None
}
}
pub fn inset(&self, dx: f32, dy: f32) -> Option<Self> {
Rect::from_ltrb(
self.left() + dx,
self.top() + dy,
self.right() - dx,
self.bottom() - dy,
)
}
pub fn outset(&self, dx: f32, dy: f32) -> Option<Self> {
self.inset(-dx, -dy)
}
pub fn transform(&self, ts: Transform) -> Option<Self> {
if !ts.is_identity() {
let mut path = PathBuilder::from_rect(*self);
path = path.transform(ts)?;
Some(path.bounds())
} else {
Some(*self)
}
}
pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self {
let x = self.x() * bbox.width() + bbox.x();
let y = self.y() * bbox.height() + bbox.y();
let w = self.width() * bbox.width();
let h = self.height() * bbox.height();
Self::from_xywh(x, y, w, h).unwrap()
}
pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> {
NonZeroRect::from_xywh(self.x(), self.y(), self.width(), self.height())
}
}
fn checked_f32_sub(a: f32, b: f32) -> Option<f32> {
debug_assert!(a.is_finite());
debug_assert!(b.is_finite());
let n = a as f64 - b as f64;
if n > f32::MIN as f64 && n < f32::MAX as f64 {
Some(n as f32)
} else {
None
}
}
#[cfg(test)]
mod rect_tests {
use super::*;
#[test]
fn tests() {
assert_eq!(Rect::from_ltrb(10.0, 10.0, 5.0, 10.0), None);
assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 5.0), None);
assert_eq!(Rect::from_ltrb(f32::NAN, 10.0, 10.0, 10.0), None);
assert_eq!(Rect::from_ltrb(10.0, f32::NAN, 10.0, 10.0), None);
assert_eq!(Rect::from_ltrb(10.0, 10.0, f32::NAN, 10.0), None);
assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::NAN), None);
assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::INFINITY), None);
let rect = Rect::from_ltrb(10.0, 20.0, 30.0, 40.0).unwrap();
assert_eq!(rect.left(), 10.0);
assert_eq!(rect.top(), 20.0);
assert_eq!(rect.right(), 30.0);
assert_eq!(rect.bottom(), 40.0);
assert_eq!(rect.width(), 20.0);
assert_eq!(rect.height(), 20.0);
let rect = Rect::from_ltrb(-30.0, 20.0, -10.0, 40.0).unwrap();
assert_eq!(rect.width(), 20.0);
assert_eq!(rect.height(), 20.0);
}
#[test]
fn round_overflow() {
let x = 128.0;
let width = i32::MAX as f32;
let rect = Rect::from_xywh(x, 0.0, width, 1.0).unwrap();
assert_eq!(rect.round(), None);
assert_eq!(rect.round_out(), None);
}
}
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq)]
pub struct NonZeroRect {
left: FiniteF32,
top: FiniteF32,
right: FiniteF32,
bottom: FiniteF32,
}
impl core::fmt::Debug for NonZeroRect {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("NonZeroRect")
.field("left", &self.left.get())
.field("top", &self.top.get())
.field("right", &self.right.get())
.field("bottom", &self.bottom.get())
.finish()
}
}
impl NonZeroRect {
pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> {
let left = FiniteF32::new(left)?;
let top = FiniteF32::new(top)?;
let right = FiniteF32::new(right)?;
let bottom = FiniteF32::new(bottom)?;
if left.get() < right.get() && top.get() < bottom.get() {
checked_f32_sub(right.get(), left.get())?;
checked_f32_sub(bottom.get(), top.get())?;
Some(Self {
left,
top,
right,
bottom,
})
} else {
None
}
}
pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
Self::from_ltrb(x, y, w + x, h + y)
}
pub fn left(&self) -> f32 {
self.left.get()
}
pub fn top(&self) -> f32 {
self.top.get()
}
pub fn right(&self) -> f32 {
self.right.get()
}
pub fn bottom(&self) -> f32 {
self.bottom.get()
}
pub fn x(&self) -> f32 {
self.left.get()
}
pub fn y(&self) -> f32 {
self.top.get()
}
pub fn width(&self) -> f32 {
self.right.get() - self.left.get()
}
pub fn height(&self) -> f32 {
self.bottom.get() - self.top.get()
}
pub fn size(&self) -> Size {
Size::from_wh(self.width(), self.height()).unwrap()
}
pub fn translate_to(&self, x: f32, y: f32) -> Option<Self> {
Self::from_xywh(x, y, self.width(), self.height())
}
pub fn transform(&self, ts: Transform) -> Option<Self> {
if !ts.is_identity() {
let mut path = PathBuilder::from_rect(self.to_rect());
path = path.transform(ts)?;
path.bounds().to_non_zero_rect()
} else {
Some(*self)
}
}
pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self {
let x = self.x() * bbox.width() + bbox.x();
let y = self.y() * bbox.height() + bbox.y();
let w = self.width() * bbox.width();
let h = self.height() * bbox.height();
Self::from_xywh(x, y, w, h).unwrap()
}
pub fn to_rect(&self) -> Rect {
Rect::from_xywh(self.x(), self.y(), self.width(), self.height()).unwrap()
}
pub fn to_int_rect(&self) -> IntRect {
IntRect::from_xywh(
self.x().floor() as i32,
self.y().floor() as i32,
core::cmp::max(1, self.width().ceil() as u32),
core::cmp::max(1, self.height().ceil() as u32),
)
.unwrap()
}
}