#![allow(clippy::expect_used)]
use std::sync::Arc;
use pdfrum::{Color, Document, Name, Rect, Subtype};
use pdfrum_edit::{
EditDoc, FreeTextSpec, SaveMode, SaveOptions, SquareSpec, add_annotation, delete_annotation,
};
const SHARED_ANNOTS: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../pdfrum/tests/fixtures/bug_498010830_shared_annots.pdf"
);
const SHARED_WIDGET: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../pdfrum/tests/fixtures/bug_498010830_shared_widget.pdf"
);
const TWO_SIGNATURES: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../pdfrum/tests/fixtures/two_signatures.pdf"
);
fn open(path: &str) -> Document {
Document::open(path).expect("open fixture")
}
fn count(doc: &Document, page: u32) -> usize {
doc.page(page).expect("page").annotations().len()
}
fn save(edit: &EditDoc<'_>) -> Document {
let mut bytes = Vec::new();
pdfrum_edit::save(edit, &SaveOptions::default(), &mut bytes).expect("save");
Document::from_bytes(Arc::from(bytes)).expect("reopen")
}
fn square() -> SquareSpec {
SquareSpec::new(
Rect::new(10.0, 10.0, 60.0, 60.0),
Color::from_rgb8(255, 0, 0),
)
}
#[test]
fn an_annotation_added_to_a_shared_array_stays_on_its_page() {
let doc = open(SHARED_ANNOTS);
let (first, second) = (count(&doc, 0), count(&doc, 1));
let mut edit = EditDoc::new(doc.parser());
add_annotation(&mut edit, 0, square()).expect("add");
let added = save(&edit);
assert_eq!(count(&added, 0), first + 1, "page 0 gains the square");
assert_eq!(count(&added, 1), second, "page 1 is untouched");
let page = added.page(0).expect("page");
let reference = page
.annotations()
.find(|a| a.subtype() == Subtype::Square)
.and_then(|a| a.obj_ref())
.expect("the square is listed indirectly");
let mut edit = EditDoc::new(added.parser());
assert!(delete_annotation(&mut edit, 0, reference).expect("delete"));
let deleted = save(&edit);
assert_eq!(count(&deleted, 0), first, "page 0 is back where it began");
assert_eq!(count(&deleted, 1), second, "page 1 is still untouched");
}
#[test]
fn a_shared_annotation_survives_deletion_from_one_page() {
let doc = open(SHARED_WIDGET);
let widget = doc
.page(0)
.expect("page")
.annotations()
.find(|a| a.subtype() == Subtype::Widget)
.and_then(|a| a.obj_ref())
.expect("page 0 lists a widget");
let second = count(&doc, 1);
assert!(second > 0, "the fixture shows the widget on page 1 as well");
let mut edit = EditDoc::new(doc.parser());
assert!(delete_annotation(&mut edit, 0, widget).expect("delete"));
let saved = save(&edit);
assert_eq!(count(&saved, 1), second, "page 1 still lists the widget");
let still = saved
.page(1)
.expect("page")
.annotations()
.any(|a| a.subtype() == Subtype::Widget && a.obj_ref() == Some(widget));
assert!(still, "the widget still resolves as a widget on page 1");
}
#[test]
fn free_text_in_a_form_without_dr_gets_an_appearance() {
let doc = open(TWO_SIGNATURES);
let form = |doc: &Document| {
let catalog = doc.parser().catalog().expect("catalog");
catalog.dict(&Name::from("AcroForm"), doc.parser())
};
assert!(
form(&doc).is_some_and(|f| f.raw(&Name::from("DR")).is_none()),
"the fixture's form has no /DR"
);
let before = count(&doc, 0);
let mut edit = EditDoc::new(doc.parser());
add_annotation(
&mut edit,
0,
FreeTextSpec::new(
Rect::new(20.0, 20.0, 220.0, 60.0),
Color::from_rgb8(0, 0, 0),
"hello".to_owned(),
),
)
.expect("add");
let mut bytes = Vec::new();
pdfrum_edit::save(
&edit,
&SaveOptions::builder().mode(SaveMode::Incremental).build(),
&mut bytes,
)
.expect("save");
let saved = Document::from_bytes(Arc::from(bytes)).expect("reopen");
assert_eq!(count(&saved, 0), before + 1);
let page = saved.page(0).expect("page");
let note = page
.annotations()
.find(|a| a.subtype() == Subtype::FreeText)
.expect("the free text");
let ap = note
.dict()
.dict(&Name::from("AP"), saved.parser())
.expect("the annotation has an /AP");
assert!(ap.raw(&Name::from("N")).is_some(), "with a normal state");
assert!(
form(&saved).is_some_and(|f| f.raw(&Name::from("DR")).is_none()),
"the document's own form is not given a /DR"
);
}