mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 22:41:47 -06:00
M14: Reuse form enumeration during document open (#1011)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Brandon Thomas
parent
3b7a01eac5
commit
db26eb266d
+17
-2
@@ -487,13 +487,28 @@ struct Inherited {
|
||||
/// Every terminal field: the `/Fields` tree in order, then the fields reachable only through the
|
||||
/// page annotations (see [`adopt_page_fields`]).
|
||||
pub fn fields(doc: &Document) -> Vec<Field> {
|
||||
enumerate(doc).0
|
||||
fields_with_adopted(doc).items
|
||||
}
|
||||
|
||||
/// How many of [`fields`] were reachable only through the page annotations: the form's `/Fields`
|
||||
/// list names none or only some of them, so the leniency that adopts them is worth recording.
|
||||
pub fn adopted_page_fields(doc: &Document) -> usize {
|
||||
enumerate(doc).1
|
||||
fields_with_adopted(doc).adopted_page_fields
|
||||
}
|
||||
|
||||
/// The terminal fields and page-only fields adopted while reading a form.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Fields {
|
||||
/// Terminal fields in tree order, followed by adopted page-only fields.
|
||||
pub items: Vec<Field>,
|
||||
/// How many fields were reachable only through page annotations.
|
||||
pub adopted_page_fields: usize,
|
||||
}
|
||||
|
||||
/// Read all terminal fields and the page-only adoption count in one enumeration.
|
||||
pub fn fields_with_adopted(doc: &Document) -> Fields {
|
||||
let (items, adopted_page_fields) = enumerate(doc);
|
||||
Fields { items, adopted_page_fields }
|
||||
}
|
||||
|
||||
fn enumerate(doc: &Document) -> (Vec<Field>, usize) {
|
||||
|
||||
@@ -62,6 +62,20 @@ fn reopen(doc: &Document) -> Document {
|
||||
Document::open(Arc::new(bytes)).expect("reopens")
|
||||
}
|
||||
|
||||
fn legacy_two_call_enumeration(doc: &Document) -> (Vec<Field>, usize) {
|
||||
(enumerate(doc).0, enumerate(doc).1)
|
||||
}
|
||||
|
||||
fn assert_combined_matches_legacy(doc: &Document, adopted: usize) {
|
||||
let (old_fields, old_adopted) = legacy_two_call_enumeration(doc);
|
||||
let combined = fields_with_adopted(doc);
|
||||
assert_eq!(combined.items, old_fields);
|
||||
assert_eq!(combined.adopted_page_fields, old_adopted);
|
||||
assert_eq!(combined.adopted_page_fields, adopted);
|
||||
assert_eq!(fields(doc), old_fields, "the compatibility wrapper keeps the old fields");
|
||||
assert_eq!(adopted_page_fields(doc), old_adopted, "the compatibility wrapper keeps the old count");
|
||||
}
|
||||
|
||||
fn field<'a>(all: &'a [Field], name: &str) -> &'a Field {
|
||||
all.iter().find(|f| f.name == name).unwrap_or_else(|| panic!("no field {name}"))
|
||||
}
|
||||
@@ -94,6 +108,7 @@ fn appearance_text(doc: &Document, w: &Widget, font_name: &[u8]) -> (Object, Vec
|
||||
#[test]
|
||||
fn the_field_tree_is_read_with_inheritance() {
|
||||
let doc = fixture();
|
||||
assert_combined_matches_legacy(&doc, 0);
|
||||
let all = fields(&doc);
|
||||
let names: Vec<&str> = all.iter().map(|f| f.name.as_str()).collect();
|
||||
assert_eq!(names, ["name", "notes", "zip", "pin", "id", "address.city", "agree", "size", "country", "toppings", "go", "bare"]);
|
||||
@@ -158,6 +173,7 @@ fn fields_listed_only_on_the_pages_are_adopted() {
|
||||
"<< /Type /Annot /Subtype /Widget /Rect [400 700 415 715] /P 3 0 R >>".into(), // 15 no /FT, /T or /Parent: not a field
|
||||
];
|
||||
let mut doc = document(&objs);
|
||||
assert_combined_matches_legacy(&doc, 3);
|
||||
let all = fields(&doc);
|
||||
let names: Vec<&str> = all.iter().map(|f| f.name.as_str()).collect();
|
||||
assert_eq!(names, ["listed", "alpha", "beta", "pair"]);
|
||||
@@ -175,6 +191,7 @@ fn fields_listed_only_on_the_pages_are_adopted() {
|
||||
let mut objs = objs;
|
||||
objs[3] = "<< /DA (/Helv 0 Tf 0 g) /DR << /Font << /Helv 6 0 R >> >> >>".into();
|
||||
let doc = document(&objs);
|
||||
assert_combined_matches_legacy(&doc, 4);
|
||||
let all = fields(&doc);
|
||||
let names: Vec<&str> = all.iter().map(|f| f.name.as_str()).collect();
|
||||
assert_eq!(names, ["listed", "alpha", "beta", "pair"]);
|
||||
@@ -499,10 +516,44 @@ fn reset_restores_defaults() {
|
||||
#[test]
|
||||
fn documents_without_forms_say_so() {
|
||||
let mut doc = Document::new_empty();
|
||||
assert_combined_matches_legacy(&doc, 0);
|
||||
assert!(fields(&doc).is_empty());
|
||||
assert_eq!(set_value(&mut doc, "x", &FieldValue::Text("y".into())), Err(FormError::NoForm));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combined_enumeration_keeps_page_order_across_pages() {
|
||||
let objs: Vec<String> = vec![
|
||||
"<< /Type /Catalog /Pages 2 0 R /AcroForm 5 0 R >>".into(),
|
||||
"<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 /MediaBox [0 0 600 800] >>".into(),
|
||||
"<< /Type /Page /Parent 2 0 R /Annots [6 0 R] >>".into(),
|
||||
"<< /Type /Page /Parent 2 0 R /Annots [7 0 R] >>".into(),
|
||||
"<< /Fields [6 0 R] >>".into(),
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Tx /T (listed) /Rect [50 700 250 720] /P 3 0 R >>".into(),
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Tx /T (adopted) /Rect [50 700 250 720] /P 4 0 R >>".into(),
|
||||
];
|
||||
let doc = document(&objs);
|
||||
assert_combined_matches_legacy(&doc, 1);
|
||||
|
||||
let combined = fields_with_adopted(&doc);
|
||||
assert_eq!(combined.items.iter().map(|f| f.name.as_str()).collect::<Vec<_>>(), ["listed", "adopted"]);
|
||||
assert_eq!(combined.items.iter().map(|f| f.widgets[0].page).collect::<Vec<_>>(), [Some(0), Some(1)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combined_enumeration_keeps_leniency_for_dangling_field_entries() {
|
||||
let objs: Vec<String> = vec![
|
||||
"<< /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >>".into(),
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 600 800] >>".into(),
|
||||
"<< /Type /Page /Parent 2 0 R /Annots [] >>".into(),
|
||||
"<< /Fields [99 0 R null 5 0 R] >>".into(),
|
||||
"<< /FT /Tx /T (valid) >>".into(),
|
||||
];
|
||||
let doc = document(&objs);
|
||||
assert_combined_matches_legacy(&doc, 0);
|
||||
assert_eq!(fields_with_adopted(&doc).items.iter().map(|f| f.name.as_str()).collect::<Vec<_>>(), ["valid"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_field_type_can_be_added_named_and_drawn() {
|
||||
let mut doc = Document::new_empty();
|
||||
@@ -1010,6 +1061,7 @@ fn field_actions_round_trip_on_every_trigger() {
|
||||
assert_eq!(field_actions(&doc, &name).unwrap(), [(Trigger::MouseExit, FieldAction::Named("NextPage".into()))]);
|
||||
// The Mouse Up script is what the button runs.
|
||||
set_field_actions(&mut doc, &name, &[(Trigger::MouseUp, FieldAction::JavaScript("this.print();".into()))]).unwrap();
|
||||
assert_combined_matches_legacy(&doc, 0);
|
||||
assert_eq!(fields(&doc).iter().find(|f| f.name == "go").unwrap().button, Some(af::ButtonAction::Named("Print".into())));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user