use pdfrum_common::PageIndex;
use pdfrum_object::{Array, Dict, Name, ObjRef, Object, Resolve, names};
use crate::doc::EditDoc;
use crate::error::Error;
use crate::import::{PageRange, init_dest, insert_into_tree};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageBox {
Media,
Crop,
Bleed,
Trim,
Art,
}
impl PageBox {
fn key(self) -> &'static Name {
match self {
Self::Media => names::MEDIA_BOX,
Self::Crop => names::CROP_BOX,
Self::Bleed => names::BLEED_BOX,
Self::Trim => names::TRIM_BOX,
Self::Art => names::ART_BOX,
}
}
}
pub fn add_blank_page(
dest: &mut EditDoc<'_>,
width: f32,
height: f32,
at: u32,
) -> Result<ObjRef, Error> {
let pages_node = init_dest(dest)?;
let page = Dict::from_pairs([
(names::TYPE.clone(), Object::Name(names::PAGE.clone())),
(
names::PARENT.clone(),
Object::Ref(ObjRef::new(pages_node, 0)),
),
(
names::MEDIA_BOX.clone(),
rect_object([0.0, 0.0, width, height]),
),
]);
let created = dest.add(Object::Dict(page));
insert_into_tree(dest, pages_node, &[created], at);
Ok(created)
}
pub fn delete_pages(dest: &mut EditDoc<'_>, range: &PageRange) -> Result<(), Error> {
let mut refs = Vec::with_capacity(range.indices().len());
for &index in range.indices() {
let page = dest
.base()
.page(index)
.map_err(|_| Error::PageIndexOutOfRange(index))?;
let Some(reference) = page.reference else {
return Err(Error::PageIndexOutOfRange(index));
};
if !refs.contains(&reference) {
refs.push(reference);
}
}
for reference in refs {
unlink(dest, reference);
}
Ok(())
}
fn unlink(dest: &mut EditDoc<'_>, page: ObjRef) {
let Some(page_dict) = dict_of(dest, page) else {
return;
};
let Some(Object::Ref(parent_ref)) = page_dict.raw(names::PARENT).cloned() else {
return;
};
let Some(mut parent) = dict_of(dest, parent_ref) else {
return;
};
let kids: Array = parent
.raw(names::KIDS)
.and_then(Object::as_array)
.map(|kids| {
kids.iter()
.filter(|kid| !matches!(kid, Object::Ref(r) if *r == page))
.cloned()
.collect()
})
.unwrap_or_default();
parent.insert(names::KIDS.clone(), Object::Array(kids));
dest.replace(parent_ref, Object::Dict(parent));
decrement_counts(dest, parent_ref);
}
fn decrement_counts(dest: &mut EditDoc<'_>, mut node_ref: ObjRef) {
for _ in 0..64 {
let Some(mut node) = dict_of(dest, node_ref) else {
return;
};
let count = node
.raw(names::COUNT)
.and_then(Object::as_int)
.unwrap_or(0)
.saturating_sub(1)
.max(0);
node.insert(names::COUNT.clone(), Object::Int(count));
let parent = node.raw(names::PARENT).cloned();
dest.replace(node_ref, Object::Dict(node));
match parent {
Some(Object::Ref(parent_ref)) => node_ref = parent_ref,
_ => return,
}
}
}
pub fn set_page_rotation(
dest: &mut EditDoc<'_>,
index: PageIndex,
degrees: i32,
) -> Result<(), Error> {
let quarter_turns =
(degrees.div_euclid(90) + i32::from(degrees.rem_euclid(90) >= 45)).rem_euclid(4);
let quarter_turns = i64::from(quarter_turns);
edit_page(dest, index, |page| {
page.insert(names::ROTATE.clone(), Object::Int(quarter_turns * 90));
})
}
pub fn set_page_box(
dest: &mut EditDoc<'_>,
index: PageIndex,
which: PageBox,
rect: [f32; 4],
) -> Result<(), Error> {
edit_page(dest, index, |page| {
page.insert(which.key().clone(), rect_object(rect));
})
}
fn edit_page(
dest: &mut EditDoc<'_>,
index: PageIndex,
change: impl FnOnce(&mut Dict),
) -> Result<(), Error> {
let page = dest
.base()
.page(index)
.map_err(|_| Error::PageIndexOutOfRange(index))?;
let Some(reference) = page.reference else {
return Err(Error::PageIndexOutOfRange(index));
};
let mut dict = dict_of(dest, reference).unwrap_or(page.dict);
change(&mut dict);
dest.replace(reference, Object::Dict(dict));
Ok(())
}
fn dict_of(dest: &EditDoc<'_>, reference: ObjRef) -> Option<Dict> {
dest.fetch(reference)
.ok()
.and_then(|object| object.as_dict().cloned())
}
fn rect_object(rect: [f32; 4]) -> Object {
Object::Array(rect.into_iter().map(Object::Real).collect())
}