use crate::bindgen::{FPDF_BOOL, FS_QUADPOINTSF};
use crate::bindings::PdfiumLibraryBindings;
use crate::error::{PdfiumError, PdfiumInternalError};
use crate::pdf::matrix::PdfMatrix;
use crate::pdf::points::PdfPoints;
use crate::pdf::rect::PdfRect;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
#[cfg(doc)]
use crate::pdf::document::page::PdfPage;
#[derive(Debug, Copy, Clone)]
pub struct PdfQuadPoints {
x1: PdfPoints,
y1: PdfPoints,
x2: PdfPoints,
y2: PdfPoints,
x3: PdfPoints,
y3: PdfPoints,
x4: PdfPoints,
y4: PdfPoints,
}
impl PdfQuadPoints {
pub const ZERO: PdfQuadPoints = PdfQuadPoints::zero();
#[inline]
pub(crate) fn from_pdfium(points: FS_QUADPOINTSF) -> Self {
PdfQuadPoints::new_from_values(
points.x1, points.y1, points.x2, points.y2, points.x3, points.y3, points.x4, points.y4,
)
}
#[inline]
pub(crate) fn from_pdfium_as_result(
result: FPDF_BOOL,
points: FS_QUADPOINTSF,
bindings: &dyn PdfiumLibraryBindings,
) -> Result<PdfQuadPoints, PdfiumError> {
if !bindings.is_true(result) {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
} else {
Ok(PdfQuadPoints::from_pdfium(points))
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub const fn new(
x1: PdfPoints,
y1: PdfPoints,
x2: PdfPoints,
y2: PdfPoints,
x3: PdfPoints,
y3: PdfPoints,
x4: PdfPoints,
y4: PdfPoints,
) -> Self {
Self {
x1,
y1,
x2,
y2,
x3,
y3,
x4,
y4,
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub const fn new_from_values(
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
x4: f32,
y4: f32,
) -> Self {
Self::new(
PdfPoints::new(x1),
PdfPoints::new(y1),
PdfPoints::new(x2),
PdfPoints::new(y2),
PdfPoints::new(x3),
PdfPoints::new(y3),
PdfPoints::new(x4),
PdfPoints::new(y4),
)
}
#[inline]
pub const fn zero() -> Self {
Self::new_from_values(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
}
#[inline]
pub fn from_rect(rect: &PdfRect) -> Self {
PdfQuadPoints::new(
rect.left(),
rect.bottom(),
rect.right(),
rect.bottom(),
rect.right(),
rect.top(),
rect.left(),
rect.top(),
)
}
#[inline]
pub fn x1(&self) -> PdfPoints {
self.x1
}
#[inline]
pub fn y1(&self) -> PdfPoints {
self.y1
}
#[inline]
pub fn x2(&self) -> PdfPoints {
self.x2
}
#[inline]
pub fn y2(&self) -> PdfPoints {
self.y2
}
#[inline]
pub fn x3(&self) -> PdfPoints {
self.x3
}
#[inline]
pub fn y3(&self) -> PdfPoints {
self.y3
}
#[inline]
pub fn x4(&self) -> PdfPoints {
self.x4
}
#[inline]
pub fn y4(&self) -> PdfPoints {
self.y4
}
pub fn left(&self) -> PdfPoints {
*vec![self.x1, self.x2, self.x3, self.x4]
.iter()
.min()
.unwrap()
}
pub fn right(&self) -> PdfPoints {
*vec![self.x1, self.x2, self.x3, self.x4]
.iter()
.max()
.unwrap()
}
pub fn bottom(&self) -> PdfPoints {
*vec![self.y1, self.y2, self.y3, self.y4]
.iter()
.min()
.unwrap()
}
pub fn top(&self) -> PdfPoints {
*vec![self.y1, self.y2, self.y3, self.y4]
.iter()
.max()
.unwrap()
}
#[inline]
pub fn width(&self) -> PdfPoints {
self.right() - self.left()
}
#[inline]
pub fn height(&self) -> PdfPoints {
self.top() - self.bottom()
}
#[inline]
pub fn transform(&self, matrix: PdfMatrix) -> PdfQuadPoints {
let (x1, y1) = matrix.apply_to_points(self.x1, self.y1);
let (x2, y2) = matrix.apply_to_points(self.x2, self.y2);
let (x3, y3) = matrix.apply_to_points(self.x3, self.y3);
let (x4, y4) = matrix.apply_to_points(self.x4, self.y4);
PdfQuadPoints::new(x1, y1, x2, y2, x3, y3, x4, y4)
}
pub fn to_rect(&self) -> PdfRect {
let xs = [self.x1, self.x2, self.x3, self.x4];
let ys = [self.y1, self.y2, self.y3, self.y4];
PdfRect::new(
*ys.iter().min().unwrap(),
*xs.iter().min().unwrap(),
*ys.iter().max().unwrap(),
*xs.iter().max().unwrap(),
)
}
#[inline]
pub(crate) fn as_pdfium(&self) -> FS_QUADPOINTSF {
FS_QUADPOINTSF {
x1: self.x1.value,
y1: self.y1.value,
x2: self.x2.value,
y2: self.y2.value,
x3: self.x3.value,
y3: self.y3.value,
x4: self.x4.value,
y4: self.y4.value,
}
}
}
impl PartialEq for PdfQuadPoints {
fn eq(&self, other: &Self) -> bool {
self.x1 == other.x1
&& self.y1 == other.y1
&& self.x2 == other.x2
&& self.y2 == other.y2
&& self.x3 == other.x3
&& self.y3 == other.y3
&& self.x4 == other.x4
&& self.y4 == other.y4
}
}
impl Eq for PdfQuadPoints {}
impl Hash for PdfQuadPoints {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u32(self.x1.value.to_bits());
state.write_u32(self.y1.value.to_bits());
state.write_u32(self.x2.value.to_bits());
state.write_u32(self.y2.value.to_bits());
state.write_u32(self.x3.value.to_bits());
state.write_u32(self.y3.value.to_bits());
state.write_u32(self.x4.value.to_bits());
state.write_u32(self.y4.value.to_bits());
}
}
impl Display for PdfQuadPoints {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"PdfQuadPoints(x1: {}, y1: {}, x2: {}, y2: {}, x3: {}, y3: {}, x4: {}, y4: {})",
self.x1.value,
self.y1.value,
self.x2.value,
self.y2.value,
self.x3.value,
self.y3.value,
self.x4.value,
self.y4.value
))
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
#[test]
fn test_quadpoints_extents() {
let r = PdfRect::new_from_values(50.0, 100.0, 300.0, 200.0);
assert_eq!(r.to_quad_points().left().value, 100.0);
assert_eq!(r.to_quad_points().right().value, 200.0);
assert_eq!(r.to_quad_points().top().value, 300.0);
assert_eq!(r.to_quad_points().bottom().value, 50.0);
assert_eq!(r.to_quad_points().width().value, 100.0);
assert_eq!(r.to_quad_points().height().value, 250.0);
}
#[test]
fn test_quadpoints_to_rect() {
let r = PdfRect::new_from_values(100.0, 100.0, 200.0, 200.0);
assert_eq!(r.to_quad_points().to_rect(), r);
let m = PdfMatrix::identity()
.rotate_clockwise_degrees(45.0)
.unwrap();
let r45 = r.transform(m);
let q = r.to_quad_points();
let q45 = q.transform(m);
assert_eq!(q.to_rect(), r);
assert_eq!(q45.to_rect(), r45);
let s = q45.transform(m.invert()).to_rect();
let threshold = PdfPoints::new(0.001);
assert!((s.top() - r.top()).abs() < threshold);
assert!((s.bottom() - r.bottom()).abs() < threshold);
assert!((s.left() - r.left()).abs() < threshold);
assert!((s.right() - r.right()).abs() < threshold);
}
}