Commit 83216e640c489a22bd4001c9c39affb72b8b1124

Authored by Jay Berkenbilt
1 parent 1f35ec99

Preserve form fields when splitting pages (fixes #340)

ChangeLog
1 1 2021-02-22 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * From qpdf CLI, --pages and --split-pages will properly preserve
  4 + interactive form functionality. Fixes #340.
  5 +
3 6 * Add QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage to
4 7 copy form fields from a foreign page into the current file.
5 8  
... ...
libqpdf/QPDFAcroFormDocumentHelper.cc
... ... @@ -676,10 +676,16 @@ QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage(
676 676 QPDFPageObjectHelper foreign_page,
677 677 QPDFAcroFormDocumentHelper& foreign_afdh)
678 678 {
  679 + std::set<QPDFObjGen> added;
679 680 for (auto field: foreign_afdh.getFormFieldsForPage(foreign_page))
680 681 {
681 682 auto new_field = this->qpdf.copyForeignObject(
682 683 field.getObjectHandle());
683   - addFormField(new_field);
  684 + auto og = new_field.getObjGen();
  685 + if (! added.count(og))
  686 + {
  687 + addFormField(new_field);
  688 + added.insert(og);
  689 + }
684 690 }
685 691 }
... ...
qpdf/qpdf.cc
... ... @@ -5143,6 +5143,19 @@ static void get_uo_pagenos(UnderOverlay&amp; uo,
5143 5143 }
5144 5144 }
5145 5145  
  5146 +static QPDFAcroFormDocumentHelper* get_afdh_for_qpdf(
  5147 + std::map<unsigned long long,
  5148 + PointerHolder<QPDFAcroFormDocumentHelper>>& afdh_map,
  5149 + QPDF* q)
  5150 +{
  5151 + auto uid = q->getUniqueId();
  5152 + if (! afdh_map.count(uid))
  5153 + {
  5154 + afdh_map[uid] = new QPDFAcroFormDocumentHelper(*q);
  5155 + }
  5156 + return afdh_map[uid].getPointer();
  5157 +}
  5158 +
5146 5159 static void do_under_overlay_for_page(
5147 5160 QPDF& pdf,
5148 5161 Options& o,
... ... @@ -5164,12 +5177,7 @@ static void do_under_overlay_for_page(
5164 5177 PointerHolder<QPDFAcroFormDocumentHelper>> afdh;
5165 5178 auto make_afdh = [&](QPDFPageObjectHelper& ph) {
5166 5179 QPDF* q = ph.getObjectHandle().getOwningQPDF();
5167   - auto uid = q->getUniqueId();
5168   - if (! afdh.count(uid))
5169   - {
5170   - afdh[uid] = new QPDFAcroFormDocumentHelper(*q);
5171   - }
5172   - return afdh[uid].getPointer();
  5180 + return get_afdh_for_qpdf(afdh, q);
5173 5181 };
5174 5182 auto dest_afdh = make_afdh(dest_page);
5175 5183  
... ... @@ -5835,6 +5843,9 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
5835 5843 std::vector<QPDFObjectHandle> new_labels;
5836 5844 bool any_page_labels = false;
5837 5845 int out_pageno = 0;
  5846 + std::map<unsigned long long,
  5847 + PointerHolder<QPDFAcroFormDocumentHelper>> afdh_map;
  5848 + auto this_afdh = get_afdh_for_qpdf(afdh_map, &pdf);
5838 5849 for (std::vector<QPDFPageData>::iterator iter =
5839 5850 parsed_specs.begin();
5840 5851 iter != parsed_specs.end(); ++iter)
... ... @@ -5847,6 +5858,7 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
5847 5858 cis->stayOpen(true);
5848 5859 }
5849 5860 QPDFPageLabelDocumentHelper pldh(*page_data.qpdf);
  5861 + auto other_afdh = get_afdh_for_qpdf(afdh_map, page_data.qpdf);
5850 5862 if (pldh.hasPageLabels())
5851 5863 {
5852 5864 any_page_labels = true;
... ... @@ -5891,6 +5903,11 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
5891 5903 // of the fact that we are using it.
5892 5904 selected_from_orig.insert(pageno);
5893 5905 }
  5906 + else if (other_afdh->hasAcroForm())
  5907 + {
  5908 + QTC::TC("qpdf", "qpdf copy form fields in pages");
  5909 + this_afdh->copyFieldsFromForeignPage(to_copy, *other_afdh);
  5910 + }
5894 5911 }
5895 5912 if (page_data.qpdf->anyWarnings())
5896 5913 {
... ... @@ -6269,6 +6286,7 @@ static void do_split_pages(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
6269 6286 dh.removeUnreferencedResources();
6270 6287 }
6271 6288 QPDFPageLabelDocumentHelper pldh(pdf);
  6289 + QPDFAcroFormDocumentHelper afdh(pdf);
6272 6290 std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
6273 6291 size_t pageno_len = QUtil::uint_to_string(pages.size()).length();
6274 6292 size_t num_pages = pages.size();
... ... @@ -6282,6 +6300,11 @@ static void do_split_pages(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
6282 6300 }
6283 6301 QPDF outpdf;
6284 6302 outpdf.emptyPDF();
  6303 + PointerHolder<QPDFAcroFormDocumentHelper> out_afdh;
  6304 + if (afdh.hasAcroForm())
  6305 + {
  6306 + out_afdh = new QPDFAcroFormDocumentHelper(outpdf);
  6307 + }
6285 6308 if (o.suppress_warnings)
6286 6309 {
6287 6310 outpdf.setSuppressWarnings(true);
... ... @@ -6290,6 +6313,12 @@ static void do_split_pages(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
6290 6313 {
6291 6314 QPDFObjectHandle page = pages.at(pageno - 1);
6292 6315 outpdf.addPage(page, false);
  6316 + if (out_afdh.getPointer())
  6317 + {
  6318 + QTC::TC("qpdf", "qpdf copy form fields in split_pages");
  6319 + out_afdh->copyFieldsFromForeignPage(
  6320 + QPDFPageObjectHelper(page), afdh);
  6321 + }
6293 6322 }
6294 6323 if (pldh.hasPageLabels())
6295 6324 {
... ...
qpdf/qpdf.testcov
... ... @@ -575,3 +575,5 @@ QPDFPageObjectHelper flatten inherit rotate 0
575 575 QPDFAcroFormDocumentHelper copy annotation 3
576 576 QPDFAcroFormDocumentHelper field with parent 3
577 577 QPDFAcroFormDocumentHelper modify ap matrix 0
  578 +qpdf copy form fields in split_pages 0
  579 +qpdf copy form fields in pages 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -2414,7 +2414,7 @@ foreach my $f (qw(screen print))
2414 2414 show_ntests();
2415 2415 # ----------
2416 2416 $td->notify("--- Copy Annotations ---");
2417   -$n_tests += 16;
  2417 +$n_tests += 21;
2418 2418  
2419 2419 $td->runtest("complex copy annotations",
2420 2420 {$td->COMMAND =>
... ... @@ -2458,6 +2458,28 @@ foreach my $d ([1, &quot;appearances-1.pdf&quot;],
2458 2458 {$td->FILE => "test80b$n.pdf"});
2459 2459 }
2460 2460  
  2461 +$td->runtest("page extraction with fields",
  2462 + {$td->COMMAND =>
  2463 + "qpdf --static-id --empty" .
  2464 + " --pages fields-two-pages.pdf -- a.pdf"},
  2465 + {$td->STRING => "", $td->EXIT_STATUS => 0},
  2466 + $td->NORMALIZE_NEWLINES);
  2467 +$td->runtest("check output",
  2468 + {$td->FILE => "a.pdf"},
  2469 + {$td->FILE => "fields-pages-out.pdf"});
  2470 +$td->runtest("page splitting with fields",
  2471 + {$td->COMMAND =>
  2472 + "qpdf --static-id" .
  2473 + " --split-pages fields-two-pages.pdf a.pdf"},
  2474 + {$td->STRING => "", $td->EXIT_STATUS => 0},
  2475 + $td->NORMALIZE_NEWLINES);
  2476 +for (my $i = 1; $i <= 2; ++$i)
  2477 +{
  2478 + $td->runtest("check output",
  2479 + {$td->FILE => "a-$i.pdf"},
  2480 + {$td->FILE => "fields-split-$i.pdf"});
  2481 +}
  2482 +
2461 2483 show_ntests();
2462 2484 # ----------
2463 2485 $td->notify("--- Page Tree Issues ---");
... ...
qpdf/qtest/qpdf/fields-pages-out.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/fields-split-1.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/fields-split-2.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/fields-two-pages.pdf 0 → 100644
No preview for this file type