use folio_core::{Matrix2D, Rect};
use folio_cos::{ObjectId, PdfObject};
#[derive(Debug, Clone)]
pub struct Page {
id: ObjectId,
dict: PdfObject,
page_num: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rotation {
None = 0,
Rotate90 = 90,
Rotate180 = 180,
Rotate270 = 270,
}
impl Rotation {
pub fn from_degrees(degrees: i64) -> Self {
match ((degrees % 360) + 360) % 360 {
90 => Rotation::Rotate90,
180 => Rotation::Rotate180,
270 => Rotation::Rotate270,
_ => Rotation::None,
}
}
pub fn degrees(&self) -> i32 {
*self as i32
}
}
impl Page {
pub(crate) fn new(id: ObjectId, dict: PdfObject, page_num: u32) -> Self {
Self { id, dict, page_num }
}
pub fn id(&self) -> ObjectId {
self.id
}
pub fn page_num(&self) -> u32 {
self.page_num
}
pub fn dict(&self) -> &PdfObject {
&self.dict
}
fn get_rect(&self, key: &[u8]) -> Option<Rect> {
let arr = self.dict.dict_get(key)?.as_array()?;
if arr.len() >= 4 {
Some(Rect::new(
arr[0].as_f64()?,
arr[1].as_f64()?,
arr[2].as_f64()?,
arr[3].as_f64()?,
))
} else {
None
}
}
pub fn media_box(&self) -> Rect {
self.get_rect(b"MediaBox")
.unwrap_or_else(|| Rect::new(0.0, 0.0, 612.0, 792.0)) }
pub fn crop_box(&self) -> Rect {
self.get_rect(b"CropBox")
.unwrap_or_else(|| self.media_box())
}
pub fn bleed_box(&self) -> Rect {
self.get_rect(b"BleedBox")
.unwrap_or_else(|| self.crop_box())
}
pub fn trim_box(&self) -> Rect {
self.get_rect(b"TrimBox").unwrap_or_else(|| self.crop_box())
}
pub fn art_box(&self) -> Rect {
self.get_rect(b"ArtBox").unwrap_or_else(|| self.crop_box())
}
pub fn rotation(&self) -> Rotation {
let degrees = self.dict.dict_get_i64(b"Rotate").unwrap_or(0);
Rotation::from_degrees(degrees)
}
pub fn width(&self) -> f64 {
let crop = self.crop_box().normalized();
match self.rotation() {
Rotation::Rotate90 | Rotation::Rotate270 => crop.height().abs(),
_ => crop.width().abs(),
}
}
pub fn height(&self) -> f64 {
let crop = self.crop_box().normalized();
match self.rotation() {
Rotation::Rotate90 | Rotation::Rotate270 => crop.width().abs(),
_ => crop.height().abs(),
}
}
pub fn num_annots(&self) -> usize {
self.dict
.dict_get(b"Annots")
.and_then(|o| o.as_array())
.map(|a| a.len())
.unwrap_or(0)
}
pub fn default_matrix(&self) -> Matrix2D {
let crop = self.crop_box().normalized();
let rot = self.rotation();
let base = Matrix2D::translation(-crop.x1, -crop.y1);
match rot {
Rotation::None => base,
Rotation::Rotate90 => {
let rotate = Matrix2D::new(0.0, -1.0, 1.0, 0.0, 0.0, crop.width());
rotate * base
}
Rotation::Rotate180 => {
let rotate = Matrix2D::new(-1.0, 0.0, 0.0, -1.0, crop.width(), crop.height());
rotate * base
}
Rotation::Rotate270 => {
let rotate = Matrix2D::new(0.0, 1.0, -1.0, 0.0, crop.height(), 0.0);
rotate * base
}
}
}
pub fn resources(&self) -> Option<&PdfObject> {
self.dict.dict_get(b"Resources")
}
pub fn contents(&self) -> Option<&PdfObject> {
self.dict.dict_get(b"Contents")
}
pub fn user_unit(&self) -> f64 {
self.dict.dict_get_f64(b"UserUnit").unwrap_or(1.0)
}
}