use std::{collections::BTreeMap, path::PathBuf};
use crate::{
args::{FlipType, OddEven},
calc::{generate_booklet_imposition, LayoutType},
error::ImpositionError,
};
use lopdf::{Dictionary, Document, Object, ObjectId, Stream};
fn update_document_pages(
doc: &mut Document,
new_kids_objects: Vec<Object>,
page_count: u32,
) -> Result<(), ImpositionError> {
let catalog_dict = doc.catalog()?;
let pages_dict_id = catalog_dict
.get(b"Pages")
.map_err(|_| {
ImpositionError::Other("Missing 'Pages' entry in Catalog dictionary".to_string())
})?
.as_reference()
.map_err(|_| {
ImpositionError::Other("'Pages' entry in Catalog is not a reference".to_string())
})?;
let pages_dict = doc
.get_object_mut(pages_dict_id)
.and_then(Object::as_dict_mut)
.map_err(|_| ImpositionError::Other("Cannot get mutable Pages dictionary".to_string()))?;
pages_dict.set(b"Kids", Object::Array(new_kids_objects));
pages_dict.set(b"Count", Object::Integer(page_count as i64));
Ok(())
}
pub fn rearrange_pdf_pages(
input_path: PathBuf,
output_path: PathBuf,
layout: LayoutType,
) -> Result<(), ImpositionError> {
let mut doc = Document::load(input_path)?;
let pages_map: BTreeMap<u32, ObjectId> = doc.get_pages();
let original_num_pages = pages_map.len() as u32;
let new_order = generate_booklet_imposition(original_num_pages, layout);
if new_order.is_empty() {
return Err(ImpositionError::Other(
"New order list cannot be empty.".to_string(),
));
}
if new_order.len() != original_num_pages as usize {
return Err(ImpositionError::Other(format!(
"New order list length ({}) must match original page count ({}).",
new_order.len(),
original_num_pages
)));
}
for &page_num in &new_order {
if page_num == 0 || page_num > original_num_pages {
return Err(ImpositionError::Other(format!(
"Invalid page number {} in new order. Must be between 1 and {}.",
page_num, original_num_pages
)));
}
}
let mut new_kids_objects: Vec<Object> = Vec::with_capacity(new_order.len());
for page_num in new_order {
if let Some(&page_id) = pages_map.get(&page_num) {
new_kids_objects.push(Object::Reference(page_id));
} else {
return Err(ImpositionError::Other(format!(
"Page {} not found in document, despite validation",
page_num
)));
}
}
update_document_pages(&mut doc, new_kids_objects, original_num_pages)?;
doc.save(output_path)?;
Ok(())
}
pub fn export_double_sided_pdf(
input_path: PathBuf,
output_path: PathBuf,
flip_type: FlipType,
odd_even: OddEven,
) -> Result<(), ImpositionError> {
let mut doc = Document::load(&input_path)?;
let pages_map: BTreeMap<u32, ObjectId> = doc.get_pages();
let total_pages = pages_map.len() as u32;
let page_order = generate_double_sided_order(total_pages, flip_type, odd_even)?;
let mut new_kids_objects = Vec::with_capacity(page_order.len());
for &page_num in &page_order {
if page_num == 0 {
let blank_page_id = create_blank_page(&mut doc)?;
new_kids_objects.push(Object::Reference(blank_page_id));
} else if let Some(&page_id) = pages_map.get(&page_num) {
new_kids_objects.push(Object::Reference(page_id));
} else {
return Err(ImpositionError::Other(format!(
"Page {} not found in document",
page_num
)));
}
}
update_document_pages(&mut doc, new_kids_objects, page_order.len() as u32)?;
doc.save(&output_path)?;
Ok(())
}
fn generate_double_sided_order(
total_pages: u32,
flip_type: FlipType,
odd_even: OddEven,
) -> Result<Vec<u32>, ImpositionError> {
let should_reverse = match (flip_type, odd_even) {
(FlipType::RR, OddEven::Odd) => true,
(FlipType::RR, OddEven::Even) => true,
(FlipType::NN, OddEven::Odd) => false,
(FlipType::NN, OddEven::Even) => false,
(FlipType::RN, OddEven::Odd) => true,
(FlipType::RN, OddEven::Even) => false,
(FlipType::NR, OddEven::Odd) => false,
(FlipType::NR, OddEven::Even) => true,
};
let mut pages = match odd_even {
OddEven::Odd => {
(1..=total_pages).step_by(2).collect::<Vec<u32>>()
}
OddEven::Even => {
let mut even_pages: Vec<u32> = (2..=total_pages).step_by(2).collect();
if total_pages % 2 == 1 {
even_pages.push(0);
}
even_pages
}
};
if should_reverse {
pages.reverse();
}
Ok(pages)
}
fn create_blank_page(doc: &mut Document) -> Result<ObjectId, ImpositionError> {
let mut page_dict = Dictionary::new();
page_dict.set(b"Type", Object::Name(b"Page".to_vec()));
let media_box = Object::Array(vec![
Object::Integer(0),
Object::Integer(0),
Object::Integer(595), Object::Integer(842), ]);
page_dict.set(b"MediaBox", media_box);
let content_stream = Stream::new(Dictionary::new(), Vec::new());
let content_id = doc.add_object(Object::Stream(content_stream));
page_dict.set(b"Contents", Object::Reference(content_id));
let page_id = doc.add_object(Object::Dictionary(page_dict));
Ok(page_id)
}