use std::path::Path;
use rpdfium_core::Rect;
use rpdfium_edit::EditDocument;
use crate::{Document, Library};
pub use rpdfium_edit::annotation_edit::{AnnotationBuilder, AnnotationSpec, AnnotationUpdates};
pub use rpdfium_edit::content_gen::{
ResourceCollector, build_resource_dict, generate_content_stream,
};
pub use rpdfium_edit::error::{EditError, EditResult};
pub use rpdfium_edit::font_reg::{FontRegistration, FontType};
pub use rpdfium_edit::form_edit::{EditFormField, EditableForm, FieldValue};
pub use rpdfium_edit::npage_export::NupPageSettings;
pub use rpdfium_edit::page_object::{
FillMode, FormObject, ImageObject, PageObject, PathObject, TextObject,
};
pub use rpdfium_edit::writer::WriteOptions;
pub use rpdfium_edit::{write_full, write_incremental};
pub struct EditableDocument<'lib> {
#[allow(dead_code)]
library: &'lib Library,
inner: EditDocument,
}
impl<'lib> EditableDocument<'lib> {
pub fn new_blank(library: &'lib Library) -> Self {
Self {
library,
inner: EditDocument::new_blank(),
}
}
pub fn from_document(doc: Document<'lib>) -> Self {
let page_ids = doc.page_ids.clone();
let catalog_id = doc.catalog_id;
let library = doc.library;
let inner = EditDocument::from_store(doc.store, page_ids, catalog_id);
Self { library, inner }
}
pub fn inner(&self) -> &EditDocument {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut EditDocument {
&mut self.inner
}
pub fn page_count(&self) -> usize {
self.inner.page_count()
}
pub fn add_page(&mut self, media_box: Rect) {
self.inner.add_page(media_box);
}
pub fn add_page_with_content(
&mut self,
media_box: Rect,
objects: &[rpdfium_edit::page_object::PageObject],
) -> Result<(), EditError> {
self.inner.add_page_with_content(media_box, objects)?;
Ok(())
}
pub fn load_standard_font(&mut self, name: &str) -> Result<FontRegistration, EditError> {
self.inner.load_standard_font(name)
}
pub fn load_font_from_data(
&mut self,
data: &[u8],
font_type: FontType,
is_cid: bool,
) -> Result<FontRegistration, EditError> {
self.inner.load_font_from_data(data, font_type, is_cid)
}
pub fn delete_page(&mut self, index: usize) -> Result<(), EditError> {
self.inner.delete_page(index)
}
pub fn add_annotation(
&mut self,
page_index: usize,
spec: AnnotationSpec,
) -> Result<rpdfium_core::error::ObjectId, EditError> {
self.inner.add_annotation(page_index, spec)
}
pub fn save(&self) -> Result<Vec<u8>, EditError> {
let options = WriteOptions::default();
let mut output = Vec::new();
write_full(&self.inner, &mut output, &options)?;
Ok(output)
}
pub fn save_with_options(&self, options: &WriteOptions) -> Result<Vec<u8>, EditError> {
let mut output = Vec::new();
write_full(&self.inner, &mut output, options)?;
Ok(output)
}
pub fn save_to_file(&self, path: impl AsRef<Path>) -> Result<(), EditError> {
let data = self.save()?;
std::fs::write(path, data)?;
Ok(())
}
pub fn save_incremental(&self, original_data: &[u8]) -> Result<Vec<u8>, EditError> {
let options = WriteOptions::default();
let mut output = Vec::new();
write_incremental(&self.inner, original_data, &mut output, &options)?;
Ok(output)
}
pub fn import_pages_from(
&mut self,
src: &EditableDocument<'_>,
src_page_indices: &[usize],
insert_at: usize,
) -> Result<(), EditError> {
rpdfium_edit::page_export::export_pages(
src.inner(),
src_page_indices,
&mut self.inner,
insert_at,
)
}
pub fn import_npage_layout(
&mut self,
src: &EditableDocument<'_>,
src_page_indices: &[usize],
dest_page_size: (f64, f64),
pages_on_x: usize,
pages_on_y: usize,
) -> Result<(), EditError> {
rpdfium_edit::npage_export::export_npage_to_one(
src.inner(),
src_page_indices,
&mut self.inner,
dest_page_size,
pages_on_x,
pages_on_y,
)
}
pub fn remove_page_object(
&mut self,
page_index: usize,
object_index: usize,
) -> Result<(), EditError> {
self.inner.remove_page_object(page_index, object_index)
}
}
impl<'lib> Document<'lib> {
pub fn into_editable(self) -> EditableDocument<'lib> {
EditableDocument::from_document(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rpdfium_core::{OpenOptions, ParsingMode, Rect};
use rpdfium_edit::annotation_edit::AnnotationBuilder;
use rpdfium_parser::store::ObjectStore;
#[test]
fn new_blank_and_save() {
let lib = Library::new();
let mut doc = EditableDocument::new_blank(&lib);
doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
let bytes = doc.save().unwrap();
assert!(bytes.starts_with(b"%PDF-"));
let store = ObjectStore::open(bytes, ParsingMode::Lenient).unwrap();
let page_ids = rpdfium_page::collect_page_ids(&store).unwrap();
assert_eq!(page_ids.len(), 1);
}
#[test]
fn add_annotation_and_save() {
let lib = Library::new();
let mut doc = EditableDocument::new_blank(&lib);
doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
let spec =
AnnotationBuilder::new(rpdfium_doc::AnnotationType::Text, [10.0, 10.0, 50.0, 50.0])
.contents("Test note")
.build();
doc.add_annotation(0, spec).unwrap();
let bytes = doc.save().unwrap();
let store = ObjectStore::open(bytes, ParsingMode::Lenient).unwrap();
let page_ids = rpdfium_page::collect_page_ids(&store).unwrap();
assert_eq!(page_ids.len(), 1);
}
#[test]
fn open_and_convert_to_editable() {
let lib = Library::new();
let mut doc = EditableDocument::new_blank(&lib);
doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
let bytes = doc.save().unwrap();
let opened = Document::open(&lib, bytes, &OpenOptions::default()).unwrap();
let initial_count = opened.page_count();
let mut editable = opened.into_editable();
editable.add_page(Rect::new(0.0, 0.0, 595.0, 842.0));
assert_eq!(editable.page_count(), initial_count as usize + 1);
let bytes2 = editable.save().unwrap();
let store = ObjectStore::open(bytes2, ParsingMode::Lenient).unwrap();
let page_ids = rpdfium_page::collect_page_ids(&store).unwrap();
assert_eq!(page_ids.len(), 2);
}
#[test]
fn page_count_matches() {
let lib = Library::new();
let mut doc = EditableDocument::new_blank(&lib);
assert_eq!(doc.page_count(), 0);
doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
assert_eq!(doc.page_count(), 1);
doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
assert_eq!(doc.page_count(), 2);
doc.delete_page(0).unwrap();
assert_eq!(doc.page_count(), 1);
}
#[test]
fn save_with_custom_options() {
let lib = Library::new();
let mut doc = EditableDocument::new_blank(&lib);
doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
let options = WriteOptions {
version: rpdfium_parser::header::PdfVersion::new(2, 0),
..Default::default()
};
let bytes = doc.save_with_options(&options).unwrap();
assert!(bytes.starts_with(b"%PDF-2.0"));
}
#[test]
fn incremental_save_round_trip() {
let lib = Library::new();
let mut doc = EditableDocument::new_blank(&lib);
doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
let original = doc.save().unwrap();
let opened = Document::open(&lib, original.clone(), &OpenOptions::default()).unwrap();
let mut editable = opened.into_editable();
editable.add_page(Rect::new(0.0, 0.0, 595.0, 842.0));
let saved = editable.save_incremental(&original).unwrap();
assert!(saved.starts_with(&original));
let store = ObjectStore::open(saved, ParsingMode::Lenient).unwrap();
let page_ids = rpdfium_page::collect_page_ids(&store).unwrap();
assert_eq!(page_ids.len(), 2);
}
}