#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PagePoint {
pub x: f64,
pub y: f64,
}
impl PagePoint {
pub fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PixelPoint {
pub x: f64,
pub y: f64,
}
impl PixelPoint {
pub fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PageRect {
pub left: f64,
pub bottom: f64,
pub right: f64,
pub top: f64,
}
impl PageRect {
pub fn new(left: f64, bottom: f64, right: f64, top: f64) -> Self {
Self {
left,
bottom,
right,
top,
}
}
pub fn width(&self) -> f64 {
self.right - self.left
}
pub fn height(&self) -> f64 {
self.top - self.bottom
}
pub fn normalized(&self) -> PageRect {
PageRect {
left: self.left.min(self.right),
right: self.left.max(self.right),
bottom: self.bottom.min(self.top),
top: self.bottom.max(self.top),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PixelRect {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
impl PixelRect {
pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
Self {
x,
y,
width,
height,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PageTransform {
fwd: [f64; 6],
inv: [f64; 6],
pixel_width: u32,
pixel_height: u32,
}
impl PageTransform {
pub(crate) fn from_corners(
pixel_width: u32,
pixel_height: u32,
origin: (f64, f64), x_axis: (f64, f64), y_axis: (f64, f64), ) -> Option<PageTransform> {
let w = f64::from(pixel_width);
let h = f64::from(pixel_height);
let a = (x_axis.0 - origin.0) / w;
let c = (x_axis.1 - origin.1) / w;
let b = (y_axis.0 - origin.0) / h;
let d = (y_axis.1 - origin.1) / h;
let (e, f) = origin;
let det = a * d - b * c;
if det == 0.0 || !det.is_finite() {
return None;
}
let ia = d / det;
let ib = -b / det;
let ic = -c / det;
let id = a / det;
let ie = -(ia * e + ib * f);
let if_ = -(ic * e + id * f);
Some(PageTransform {
fwd: [a, b, c, d, e, f],
inv: [ia, ib, ic, id, ie, if_],
pixel_width,
pixel_height,
})
}
pub fn pixel_width(&self) -> u32 {
self.pixel_width
}
pub fn pixel_height(&self) -> u32 {
self.pixel_height
}
pub fn pixel_to_page(&self, p: PixelPoint) -> PagePoint {
let [a, b, c, d, e, f] = self.fwd;
PagePoint::new(a * p.x + b * p.y + e, c * p.x + d * p.y + f)
}
pub fn page_to_pixel(&self, p: PagePoint) -> PixelPoint {
let [a, b, c, d, e, f] = self.inv;
PixelPoint::new(a * p.x + b * p.y + e, c * p.x + d * p.y + f)
}
pub fn pixel_rect_to_page(&self, r: PixelRect) -> PageRect {
let p1 = self.pixel_to_page(PixelPoint::new(r.x, r.y));
let p2 = self.pixel_to_page(PixelPoint::new(r.x + r.width, r.y + r.height));
PageRect::new(p1.x, p1.y, p2.x, p2.y).normalized()
}
pub fn page_rect_to_pixel(&self, r: PageRect) -> PixelRect {
let p1 = self.page_to_pixel(PagePoint::new(r.left, r.top));
let p2 = self.page_to_pixel(PagePoint::new(r.right, r.bottom));
let x = p1.x.min(p2.x);
let y = p1.y.min(p2.y);
PixelRect::new(x, y, (p2.x - p1.x).abs(), (p2.y - p1.y).abs())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_like_roundtrip() {
let t = PageTransform::from_corners(400, 200, (0.0, 100.0), (200.0, 100.0), (0.0, 0.0))
.unwrap();
let p = t.pixel_to_page(PixelPoint::new(100.0, 50.0));
assert!((p.x - 50.0).abs() < 1e-9);
assert!((p.y - 75.0).abs() < 1e-9);
let d = t.page_to_pixel(p);
assert!((d.x - 100.0).abs() < 1e-9);
assert!((d.y - 50.0).abs() < 1e-9);
}
#[test]
fn degenerate_corners_rejected() {
assert!(
PageTransform::from_corners(100, 100, (0.0, 0.0), (0.0, 0.0), (0.0, 0.0)).is_none()
);
}
}