use super::{OperationError, OperationResult};
use crate::parser::page_tree::ParsedPage;
use crate::parser::{PdfDocument, PdfReader};
use crate::{Document, Page};
use std::fs::File;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct ReorderOptions {
pub page_order: Vec<usize>,
pub preserve_metadata: bool,
pub optimize: bool,
}
impl Default for ReorderOptions {
fn default() -> Self {
Self {
page_order: Vec::new(),
preserve_metadata: true,
optimize: false,
}
}
}
pub struct PageReorderer {
document: PdfDocument<File>,
options: ReorderOptions,
}
impl PageReorderer {
pub fn new(document: PdfDocument<File>, options: ReorderOptions) -> Self {
Self { document, options }
}
pub fn reorder(&self) -> OperationResult<Document> {
let total_pages =
self.document
.page_count()
.map_err(|e| OperationError::ParseError(e.to_string()))? as usize;
if total_pages == 0 {
return Err(OperationError::NoPagesToProcess);
}
self.validate_page_order(total_pages)?;
let mut output_doc = Document::new();
if self.options.preserve_metadata {
self.copy_metadata(&mut output_doc)?;
}
for &page_idx in &self.options.page_order {
let parsed_page = self
.document
.get_page(page_idx as u32)
.map_err(|e| OperationError::ParseError(e.to_string()))?;
let page = self.convert_page(&parsed_page)?;
output_doc.add_page(page);
}
Ok(output_doc)
}
pub fn reorder_to_file<P: AsRef<Path>>(&self, output_path: P) -> OperationResult<()> {
let mut doc = self.reorder()?;
doc.save(output_path)?;
Ok(())
}
fn validate_page_order(&self, total_pages: usize) -> OperationResult<()> {
if self.options.page_order.is_empty() {
return Err(OperationError::InvalidPageRange(
"Page order cannot be empty".to_string(),
));
}
for &idx in &self.options.page_order {
if idx >= total_pages {
return Err(OperationError::InvalidPageRange(format!(
"Page index {idx} is out of bounds (document has {total_pages} pages)"
)));
}
}
Ok(())
}
fn copy_metadata(&self, doc: &mut Document) -> OperationResult<()> {
if let Ok(metadata) = self.document.metadata() {
if let Some(title) = metadata.title {
doc.set_title(&title);
}
if let Some(author) = metadata.author {
doc.set_author(&author);
}
if let Some(subject) = metadata.subject {
doc.set_subject(&subject);
}
if let Some(keywords) = metadata.keywords {
doc.set_keywords(&keywords);
}
}
Ok(())
}
fn convert_page(&self, parsed_page: &ParsedPage) -> OperationResult<Page> {
Page::from_parsed_with_content(parsed_page, &self.document)
.map_err(|e| OperationError::ParseError(e.to_string()))
}
}
pub fn reorder_pdf_pages<P: AsRef<Path>, Q: AsRef<Path>>(
input_path: P,
output_path: Q,
page_order: Vec<usize>,
) -> OperationResult<()> {
let document = PdfReader::open_document(input_path)
.map_err(|e| OperationError::ParseError(e.to_string()))?;
let options = ReorderOptions {
page_order,
preserve_metadata: true,
optimize: false,
};
let reorderer = PageReorderer::new(document, options);
reorderer.reorder_to_file(output_path)
}
pub fn reverse_pdf_pages<P: AsRef<Path>, Q: AsRef<Path>>(
input_path: P,
output_path: Q,
) -> OperationResult<()> {
let document = PdfReader::open_document(&input_path)
.map_err(|e| OperationError::ParseError(e.to_string()))?;
let page_count = document
.page_count()
.map_err(|e| OperationError::ParseError(e.to_string()))? as usize;
let page_order: Vec<usize> = (0..page_count).rev().collect();
reorder_pdf_pages(input_path, output_path, page_order)
}
pub fn move_pdf_page<P: AsRef<Path>, Q: AsRef<Path>>(
input_path: P,
output_path: Q,
from_index: usize,
to_index: usize,
) -> OperationResult<()> {
let document = PdfReader::open_document(&input_path)
.map_err(|e| OperationError::ParseError(e.to_string()))?;
let page_count = document
.page_count()
.map_err(|e| OperationError::ParseError(e.to_string()))? as usize;
if from_index >= page_count || to_index >= page_count {
return Err(OperationError::InvalidPageRange(
"Page index out of bounds".to_string(),
));
}
let mut page_order: Vec<usize> = (0..page_count).collect();
let page = page_order.remove(from_index);
page_order.insert(to_index, page);
reorder_pdf_pages(input_path, output_path, page_order)
}
pub fn swap_pdf_pages<P: AsRef<Path>, Q: AsRef<Path>>(
input_path: P,
output_path: Q,
page1: usize,
page2: usize,
) -> OperationResult<()> {
let document = PdfReader::open_document(&input_path)
.map_err(|e| OperationError::ParseError(e.to_string()))?;
let page_count = document
.page_count()
.map_err(|e| OperationError::ParseError(e.to_string()))? as usize;
if page1 >= page_count || page2 >= page_count {
return Err(OperationError::InvalidPageRange(
"Page index out of bounds".to_string(),
));
}
let mut page_order: Vec<usize> = (0..page_count).collect();
page_order.swap(page1, page2);
reorder_pdf_pages(input_path, output_path, page_order)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reorder_options_default() {
let options = ReorderOptions::default();
assert!(options.page_order.is_empty());
assert!(options.preserve_metadata);
assert!(!options.optimize);
}
#[test]
fn test_reorder_options_custom() {
let options = ReorderOptions {
page_order: vec![2, 0, 1],
preserve_metadata: false,
optimize: true,
};
assert_eq!(options.page_order, vec![2, 0, 1]);
assert!(!options.preserve_metadata);
assert!(options.optimize);
}
#[test]
fn test_validate_page_order_empty() {
use crate::{Document, Page};
use tempfile::NamedTempFile;
let mut doc = Document::new();
doc.add_page(Page::a4());
let temp_file = NamedTempFile::new().unwrap();
doc.save(temp_file.path()).unwrap();
let pdf_doc = PdfReader::open_document(temp_file.path()).unwrap();
let options = ReorderOptions {
page_order: vec![],
preserve_metadata: true,
optimize: false,
};
let reorderer = PageReorderer::new(pdf_doc, options);
let result = reorderer.reorder();
assert!(result.is_err());
if let Err(OperationError::InvalidPageRange(msg)) = result {
assert!(msg.contains("empty"));
} else {
panic!("Expected InvalidPageRange error");
}
}
#[test]
fn test_validate_page_order_out_of_bounds() {
use crate::{Document, Page};
use tempfile::NamedTempFile;
let mut doc = Document::new();
doc.add_page(Page::a4());
doc.add_page(Page::letter());
let temp_file = NamedTempFile::new().unwrap();
doc.save(temp_file.path()).unwrap();
let pdf_doc = PdfReader::open_document(temp_file.path()).unwrap();
let options = ReorderOptions {
page_order: vec![0, 5], preserve_metadata: true,
optimize: false,
};
let reorderer = PageReorderer::new(pdf_doc, options);
let result = reorderer.reorder();
assert!(result.is_err());
if let Err(OperationError::InvalidPageRange(msg)) = result {
assert!(msg.contains("out of bounds"));
} else {
panic!("Expected InvalidPageRange error");
}
}
#[test]
fn test_reorder_pages_simple() {
use crate::{Document, Page};
use tempfile::NamedTempFile;
let mut doc = Document::new();
let mut page1 = Page::a4();
page1.graphics().begin_text();
page1.graphics().set_text_position(100.0, 700.0);
let _ = page1.graphics().show_text("Page 1");
page1.graphics().end_text();
doc.add_page(page1);
let mut page2 = Page::a4();
page2.graphics().begin_text();
page2.graphics().set_text_position(100.0, 700.0);
let _ = page2.graphics().show_text("Page 2");
page2.graphics().end_text();
doc.add_page(page2);
let mut page3 = Page::a4();
page3.graphics().begin_text();
page3.graphics().set_text_position(100.0, 700.0);
let _ = page3.graphics().show_text("Page 3");
page3.graphics().end_text();
doc.add_page(page3);
let temp_file = NamedTempFile::new().unwrap();
doc.save(temp_file.path()).unwrap();
let pdf_doc = PdfReader::open_document(temp_file.path()).unwrap();
let options = ReorderOptions {
page_order: vec![2, 0, 1],
preserve_metadata: true,
optimize: false,
};
let reorderer = PageReorderer::new(pdf_doc, options);
let result = reorderer.reorder();
assert!(result.is_ok());
let reordered_doc = result.unwrap();
assert_eq!(reordered_doc.page_count(), 3);
}
#[test]
fn test_reverse_pages() {
use crate::{Document, Page};
use tempfile::NamedTempFile;
let mut doc = Document::new();
for i in 1..=4 {
let mut page = Page::a4();
page.graphics().begin_text();
page.graphics().set_text_position(100.0, 700.0);
let _ = page.graphics().show_text(&format!("Page {}", i));
page.graphics().end_text();
doc.add_page(page);
}
let temp_input = NamedTempFile::new().unwrap();
doc.save(temp_input.path()).unwrap();
let temp_output = NamedTempFile::new().unwrap();
let result = reverse_pdf_pages(temp_input.path(), temp_output.path());
assert!(result.is_ok());
assert!(temp_output.path().exists());
}
#[test]
fn test_swap_pages() {
use crate::{Document, Page};
use tempfile::NamedTempFile;
let mut doc = Document::new();
doc.add_page(Page::a4());
doc.add_page(Page::letter());
doc.add_page(Page::legal());
let temp_input = NamedTempFile::new().unwrap();
doc.save(temp_input.path()).unwrap();
let temp_output = NamedTempFile::new().unwrap();
let result = swap_pdf_pages(temp_input.path(), temp_output.path(), 0, 2);
assert!(result.is_ok());
let result = swap_pdf_pages(temp_input.path(), temp_output.path(), 0, 10);
assert!(result.is_err());
}
#[test]
fn test_move_page() {
use crate::{Document, Page};
use tempfile::NamedTempFile;
let mut doc = Document::new();
for _ in 0..5 {
doc.add_page(Page::a4());
}
let temp_input = NamedTempFile::new().unwrap();
doc.save(temp_input.path()).unwrap();
let temp_output = NamedTempFile::new().unwrap();
let result = move_pdf_page(temp_input.path(), temp_output.path(), 0, 3);
assert!(result.is_ok());
let result = move_pdf_page(temp_input.path(), temp_output.path(), 10, 2);
assert!(result.is_err());
}
#[test]
fn test_duplicate_pages_in_order() {
use crate::{Document, Page};
use tempfile::NamedTempFile;
let mut doc = Document::new();
doc.add_page(Page::a4());
doc.add_page(Page::letter());
let temp_file = NamedTempFile::new().unwrap();
doc.save(temp_file.path()).unwrap();
let pdf_doc = PdfReader::open_document(temp_file.path()).unwrap();
let options = ReorderOptions {
page_order: vec![0, 1, 0, 1],
preserve_metadata: true,
optimize: false,
};
let reorderer = PageReorderer::new(pdf_doc, options);
let result = reorderer.reorder();
assert!(result.is_ok());
let reordered_doc = result.unwrap();
assert_eq!(reordered_doc.page_count(), 4); }
#[test]
fn test_single_page_reorder() {
use crate::{Document, Page};
use tempfile::NamedTempFile;
let mut doc = Document::new();
doc.add_page(Page::a4());
let temp_file = NamedTempFile::new().unwrap();
doc.save(temp_file.path()).unwrap();
let pdf_doc = PdfReader::open_document(temp_file.path()).unwrap();
let options = ReorderOptions {
page_order: vec![0],
preserve_metadata: true,
optimize: false,
};
let reorderer = PageReorderer::new(pdf_doc, options);
let result = reorderer.reorder();
assert!(result.is_ok());
let reordered_doc = result.unwrap();
assert_eq!(reordered_doc.page_count(), 1);
}
}
#[cfg(test)]
#[path = "reorder_tests.rs"]
mod reorder_tests;