use crate::bindgen::{FPDF_BOOL, FS_RECTF};
use crate::bindings::PdfiumLibraryBindings;
use crate::error::{PdfiumError, PdfiumInternalError};
use crate::pdf::matrix::PdfMatrix;
use crate::pdf::points::PdfPoints;
use crate::pdf::quad_points::PdfQuadPoints;
use itertools::{max, min};
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
#[cfg(doc)]
use crate::pdf::document::page::PdfPage;
#[derive(Debug, Copy, Clone)]
pub struct PdfRect {
bottom: PdfPoints,
left: PdfPoints,
top: PdfPoints,
right: PdfPoints,
}
impl PdfRect {
pub const ZERO: PdfRect = PdfRect::zero();
pub const MAX: PdfRect = PdfRect::new(
PdfPoints::MIN,
PdfPoints::MIN,
PdfPoints::MAX,
PdfPoints::MAX,
);
#[inline]
pub(crate) fn from_pdfium(rect: FS_RECTF) -> Self {
Self::new_from_values(rect.bottom, rect.left, rect.top, rect.right)
}
#[inline]
pub(crate) fn from_pdfium_as_result(
result: FPDF_BOOL,
rect: FS_RECTF,
bindings: &dyn PdfiumLibraryBindings,
) -> Result<PdfRect, PdfiumError> {
if !bindings.is_true(result) {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
} else {
Ok(PdfRect::from_pdfium(rect))
}
}
#[inline]
pub const fn new(bottom: PdfPoints, left: PdfPoints, top: PdfPoints, right: PdfPoints) -> Self {
let (ordered_bottom, ordered_top) = if bottom.value > top.value {
(top, bottom)
} else {
(bottom, top)
};
let (ordered_left, ordered_right) = if left.value > right.value {
(right, left)
} else {
(left, right)
};
Self {
bottom: ordered_bottom,
left: ordered_left,
top: ordered_top,
right: ordered_right,
}
}
#[inline]
pub const fn new_from_values(bottom: f32, left: f32, top: f32, right: f32) -> Self {
Self::new(
PdfPoints::new(bottom),
PdfPoints::new(left),
PdfPoints::new(top),
PdfPoints::new(right),
)
}
#[inline]
pub const fn zero() -> Self {
Self::new_from_values(0.0, 0.0, 0.0, 0.0)
}
#[inline]
pub const fn left(&self) -> PdfPoints {
self.left
}
#[inline]
pub const fn right(&self) -> PdfPoints {
self.right
}
#[inline]
pub const fn bottom(&self) -> PdfPoints {
self.bottom
}
#[inline]
pub const fn top(&self) -> PdfPoints {
self.top
}
#[inline]
pub fn width(&self) -> PdfPoints {
self.right() - self.left()
}
#[inline]
pub fn height(&self) -> PdfPoints {
self.top() - self.bottom()
}
#[inline]
pub fn contains(&self, x: PdfPoints, y: PdfPoints) -> bool {
self.contains_x(x) && self.contains_y(y)
}
#[inline]
pub fn contains_x(&self, x: PdfPoints) -> bool {
self.left() <= x && self.right() >= x
}
#[inline]
pub fn contains_y(&self, y: PdfPoints) -> bool {
self.bottom() <= y && self.top() >= y
}
#[inline]
pub fn is_inside(&self, other: &PdfRect) -> bool {
self.left() >= other.left()
&& self.right() <= other.right()
&& self.top() <= other.top()
&& self.bottom() >= other.bottom()
}
#[inline]
pub fn does_overlap(&self, other: &PdfRect) -> bool {
self.left() < other.right()
&& self.right() > other.left()
&& self.top() > other.bottom()
&& self.bottom() < other.top()
}
#[inline]
pub fn transform(&self, matrix: PdfMatrix) -> PdfRect {
let (x1, y1) = matrix.apply_to_points(self.left(), self.top());
let (x2, y2) = matrix.apply_to_points(self.left(), self.bottom());
let (x3, y3) = matrix.apply_to_points(self.right(), self.top());
let (x4, y4) = matrix.apply_to_points(self.right(), self.bottom());
PdfRect::new(
min([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
min([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
max([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
max([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
)
}
#[inline]
pub fn to_quad_points(&self) -> PdfQuadPoints {
PdfQuadPoints::from_rect(self)
}
#[inline]
pub(crate) fn as_pdfium(&self) -> FS_RECTF {
FS_RECTF {
left: self.left().value,
top: self.top().value,
right: self.right().value,
bottom: self.bottom().value,
}
}
}
impl PartialEq for PdfRect {
fn eq(&self, other: &Self) -> bool {
self.bottom() == other.bottom()
&& self.left() == other.left()
&& self.top() == other.top()
&& self.right() == other.right()
}
}
impl Eq for PdfRect {}
impl Hash for PdfRect {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u32(self.bottom().value.to_bits());
state.write_u32(self.left().value.to_bits());
state.write_u32(self.top().value.to_bits());
state.write_u32(self.right().value.to_bits());
}
}
impl Display for PdfRect {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"PdfRect(bottom: {}, left: {}, top: {}, right: {})",
self.bottom().value,
self.left().value,
self.top().value,
self.right().value
))
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
#[test]
fn test_rect_is_inside() {
assert!(PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0)
.is_inside(&PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0)));
assert!(!PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0)
.is_inside(&PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0)));
assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
.is_inside(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0)));
assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
.is_inside(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0)));
assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
.is_inside(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0)));
}
#[test]
fn test_rect_does_overlap() {
assert!(PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
.does_overlap(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0)));
assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
.does_overlap(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0)));
assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
.does_overlap(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0)));
}
#[test]
fn test_transform_rect() {
let delta_x = PdfPoints::new(50.0);
let delta_y = PdfPoints::new(-25.0);
let matrix = PdfMatrix::identity().translate(delta_x, delta_y).unwrap();
let bottom = PdfPoints::new(100.0);
let top = PdfPoints::new(200.0);
let left = PdfPoints::new(300.0);
let right = PdfPoints::new(400.0);
let rect = PdfRect::new(bottom, left, top, right);
let result = rect.transform(matrix);
assert_eq!(result.bottom(), bottom + delta_y);
assert_eq!(result.top(), top + delta_y);
assert_eq!(result.left(), left + delta_x);
assert_eq!(result.right(), right + delta_x);
}
#[test]
fn test_coordinate_space_order_guard() {
let result = PdfRect::new_from_values(
149.0, 544.0, 73.0, 48.0, );
assert_eq!(result.bottom().value, 73.0);
assert_eq!(result.top().value, 149.0);
assert_eq!(result.left().value, 48.0);
assert_eq!(result.right().value, 544.0);
}
}