use crate::objects::PdfObj;
use crate::page_tree::PageInfo;
use crate::resolver::Resolver;
#[derive(Debug, Clone, PartialEq)]
pub struct PageBoxes {
pub media_box: [f64; 4],
pub crop_box: Option<[f64; 4]>,
pub bleed_box: Option<[f64; 4]>,
pub trim_box: Option<[f64; 4]>,
pub art_box: Option<[f64; 4]>,
pub rotate: u16,
pub user_unit: f64,
pub duration: Option<f64>,
pub has_transition: bool,
pub has_additional_actions: bool,
}
pub fn parse_page_boxes(
resolver: &Resolver,
pages: &[PageInfo],
page_index: usize,
) -> Option<PageBoxes> {
let info = pages.get(page_index)?;
let mut boxes = PageBoxes {
media_box: info.media_box,
crop_box: None,
bleed_box: None,
trim_box: None,
art_box: None,
rotate: rotate_from_info(info.rotate),
user_unit: 1.0,
duration: None,
has_transition: false,
has_additional_actions: false,
};
if info.crop_box != info.media_box {
boxes.crop_box = Some(info.crop_box);
}
if let Ok(obj) = resolver.resolve(info.obj_num, 0)
&& let Some(dict) = obj.as_dict()
{
boxes.bleed_box = dict.get_array(b"BleedBox").and_then(parse_box);
boxes.trim_box = dict.get_array(b"TrimBox").and_then(parse_box);
boxes.art_box = dict.get_array(b"ArtBox").and_then(parse_box);
if let Some(cb) = dict.get_array(b"CropBox").and_then(parse_box) {
boxes.crop_box = Some(cb);
}
if let Some(uu) = dict.get_f64(b"UserUnit")
&& uu > 0.0
{
boxes.user_unit = uu;
}
boxes.duration = dict.get_f64(b"Dur");
boxes.has_transition = dict.get(b"Trans").is_some();
boxes.has_additional_actions = dict.get(b"AA").is_some();
}
Some(boxes)
}
fn parse_box(arr: &[PdfObj]) -> Option<[f64; 4]> {
if arr.len() < 4 {
return None;
}
Some([
arr[0].as_f64()?,
arr[1].as_f64()?,
arr[2].as_f64()?,
arr[3].as_f64()?,
])
}
fn rotate_from_info(rot: i32) -> u16 {
let mut r = rot.rem_euclid(360);
if r % 90 != 0 {
r = 0;
}
r as u16
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rotate_normalizes_negative() {
assert_eq!(rotate_from_info(-90), 270);
assert_eq!(rotate_from_info(0), 0);
assert_eq!(rotate_from_info(90), 90);
assert_eq!(rotate_from_info(360), 0);
assert_eq!(rotate_from_info(450), 90);
}
#[test]
fn rotate_drops_non_multiples_of_90() {
assert_eq!(rotate_from_info(45), 0);
assert_eq!(rotate_from_info(180 + 1), 0);
}
#[test]
fn parse_box_requires_four_numbers() {
let arr = vec![PdfObj::Real(0.0), PdfObj::Real(0.0), PdfObj::Real(100.0)];
assert!(parse_box(&arr).is_none());
let arr = vec![
PdfObj::Int(0),
PdfObj::Int(0),
PdfObj::Int(612),
PdfObj::Int(792),
];
assert_eq!(parse_box(&arr), Some([0.0, 0.0, 612.0, 792.0]));
}
#[test]
fn parse_box_mixed_int_real() {
let arr = vec![
PdfObj::Int(0),
PdfObj::Real(0.5),
PdfObj::Int(612),
PdfObj::Real(792.0),
];
assert_eq!(parse_box(&arr), Some([0.0, 0.5, 612.0, 792.0]));
}
#[test]
fn parse_box_rejects_non_numeric() {
let arr = vec![
PdfObj::Int(0),
PdfObj::Int(0),
PdfObj::Name(b"oops".to_vec()),
PdfObj::Int(792),
];
assert!(parse_box(&arr).is_none());
}
}