diff --git a/ChangeLog b/ChangeLog index 8693e5d4..ab4bb571 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2021-02-22 Jay Berkenbilt + * From qpdf CLI, --pages and --split-pages will properly preserve + interactive form functionality. Fixes #340. + * Add QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage to copy form fields from a foreign page into the current file. diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc index dce413bd..611d469a 100644 --- a/libqpdf/QPDFAcroFormDocumentHelper.cc +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -676,10 +676,16 @@ QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage( QPDFPageObjectHelper foreign_page, QPDFAcroFormDocumentHelper& foreign_afdh) { + std::set added; for (auto field: foreign_afdh.getFormFieldsForPage(foreign_page)) { auto new_field = this->qpdf.copyForeignObject( field.getObjectHandle()); - addFormField(new_field); + auto og = new_field.getObjGen(); + if (! added.count(og)) + { + addFormField(new_field); + added.insert(og); + } } } diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index 0935e4f8..2080a44a 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -5143,6 +5143,19 @@ static void get_uo_pagenos(UnderOverlay& uo, } } +static QPDFAcroFormDocumentHelper* get_afdh_for_qpdf( + std::map>& afdh_map, + QPDF* q) +{ + auto uid = q->getUniqueId(); + if (! afdh_map.count(uid)) + { + afdh_map[uid] = new QPDFAcroFormDocumentHelper(*q); + } + return afdh_map[uid].getPointer(); +} + static void do_under_overlay_for_page( QPDF& pdf, Options& o, @@ -5164,12 +5177,7 @@ static void do_under_overlay_for_page( PointerHolder> afdh; auto make_afdh = [&](QPDFPageObjectHelper& ph) { QPDF* q = ph.getObjectHandle().getOwningQPDF(); - auto uid = q->getUniqueId(); - if (! afdh.count(uid)) - { - afdh[uid] = new QPDFAcroFormDocumentHelper(*q); - } - return afdh[uid].getPointer(); + return get_afdh_for_qpdf(afdh, q); }; auto dest_afdh = make_afdh(dest_page); @@ -5835,6 +5843,9 @@ static void handle_page_specs(QPDF& pdf, Options& o, bool& warnings) std::vector new_labels; bool any_page_labels = false; int out_pageno = 0; + std::map> afdh_map; + auto this_afdh = get_afdh_for_qpdf(afdh_map, &pdf); for (std::vector::iterator iter = parsed_specs.begin(); iter != parsed_specs.end(); ++iter) @@ -5847,6 +5858,7 @@ static void handle_page_specs(QPDF& pdf, Options& o, bool& warnings) cis->stayOpen(true); } QPDFPageLabelDocumentHelper pldh(*page_data.qpdf); + auto other_afdh = get_afdh_for_qpdf(afdh_map, page_data.qpdf); if (pldh.hasPageLabels()) { any_page_labels = true; @@ -5891,6 +5903,11 @@ static void handle_page_specs(QPDF& pdf, Options& o, bool& warnings) // of the fact that we are using it. selected_from_orig.insert(pageno); } + else if (other_afdh->hasAcroForm()) + { + QTC::TC("qpdf", "qpdf copy form fields in pages"); + this_afdh->copyFieldsFromForeignPage(to_copy, *other_afdh); + } } if (page_data.qpdf->anyWarnings()) { @@ -6269,6 +6286,7 @@ static void do_split_pages(QPDF& pdf, Options& o, bool& warnings) dh.removeUnreferencedResources(); } QPDFPageLabelDocumentHelper pldh(pdf); + QPDFAcroFormDocumentHelper afdh(pdf); std::vector const& pages = pdf.getAllPages(); size_t pageno_len = QUtil::uint_to_string(pages.size()).length(); size_t num_pages = pages.size(); @@ -6282,6 +6300,11 @@ static void do_split_pages(QPDF& pdf, Options& o, bool& warnings) } QPDF outpdf; outpdf.emptyPDF(); + PointerHolder out_afdh; + if (afdh.hasAcroForm()) + { + out_afdh = new QPDFAcroFormDocumentHelper(outpdf); + } if (o.suppress_warnings) { outpdf.setSuppressWarnings(true); @@ -6290,6 +6313,12 @@ static void do_split_pages(QPDF& pdf, Options& o, bool& warnings) { QPDFObjectHandle page = pages.at(pageno - 1); outpdf.addPage(page, false); + if (out_afdh.getPointer()) + { + QTC::TC("qpdf", "qpdf copy form fields in split_pages"); + out_afdh->copyFieldsFromForeignPage( + QPDFPageObjectHelper(page), afdh); + } } if (pldh.hasPageLabels()) { diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index bbc23dfc..a68e88df 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -575,3 +575,5 @@ QPDFPageObjectHelper flatten inherit rotate 0 QPDFAcroFormDocumentHelper copy annotation 3 QPDFAcroFormDocumentHelper field with parent 3 QPDFAcroFormDocumentHelper modify ap matrix 0 +qpdf copy form fields in split_pages 0 +qpdf copy form fields in pages 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 09df4258..ff660118 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -2414,7 +2414,7 @@ foreach my $f (qw(screen print)) show_ntests(); # ---------- $td->notify("--- Copy Annotations ---"); -$n_tests += 16; +$n_tests += 21; $td->runtest("complex copy annotations", {$td->COMMAND => @@ -2458,6 +2458,28 @@ foreach my $d ([1, "appearances-1.pdf"], {$td->FILE => "test80b$n.pdf"}); } +$td->runtest("page extraction with fields", + {$td->COMMAND => + "qpdf --static-id --empty" . + " --pages fields-two-pages.pdf -- a.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "fields-pages-out.pdf"}); +$td->runtest("page splitting with fields", + {$td->COMMAND => + "qpdf --static-id" . + " --split-pages fields-two-pages.pdf a.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +for (my $i = 1; $i <= 2; ++$i) +{ + $td->runtest("check output", + {$td->FILE => "a-$i.pdf"}, + {$td->FILE => "fields-split-$i.pdf"}); +} + show_ntests(); # ---------- $td->notify("--- Page Tree Issues ---"); diff --git a/qpdf/qtest/qpdf/fields-pages-out.pdf b/qpdf/qtest/qpdf/fields-pages-out.pdf new file mode 100644 index 00000000..8b3eaa83 Binary files /dev/null and b/qpdf/qtest/qpdf/fields-pages-out.pdf differ diff --git a/qpdf/qtest/qpdf/fields-split-1.pdf b/qpdf/qtest/qpdf/fields-split-1.pdf new file mode 100644 index 00000000..9f8a7870 Binary files /dev/null and b/qpdf/qtest/qpdf/fields-split-1.pdf differ diff --git a/qpdf/qtest/qpdf/fields-split-2.pdf b/qpdf/qtest/qpdf/fields-split-2.pdf new file mode 100644 index 00000000..909d0ac3 Binary files /dev/null and b/qpdf/qtest/qpdf/fields-split-2.pdf differ diff --git a/qpdf/qtest/qpdf/fields-two-pages.pdf b/qpdf/qtest/qpdf/fields-two-pages.pdf new file mode 100644 index 00000000..d8f3095f Binary files /dev/null and b/qpdf/qtest/qpdf/fields-two-pages.pdf differ