Commit 83216e640c489a22bd4001c9c39affb72b8b1124

Authored by Jay Berkenbilt
1 parent 1f35ec99

Preserve form fields when splitting pages (fixes #340)

ChangeLog
1 2021-02-22 Jay Berkenbilt <ejb@ql.org> 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 * Add QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage to 6 * Add QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage to
4 copy form fields from a foreign page into the current file. 7 copy form fields from a foreign page into the current file.
5 8
libqpdf/QPDFAcroFormDocumentHelper.cc
@@ -676,10 +676,16 @@ QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage( @@ -676,10 +676,16 @@ QPDFAcroFormDocumentHelper::copyFieldsFromForeignPage(
676 QPDFPageObjectHelper foreign_page, 676 QPDFPageObjectHelper foreign_page,
677 QPDFAcroFormDocumentHelper& foreign_afdh) 677 QPDFAcroFormDocumentHelper& foreign_afdh)
678 { 678 {
  679 + std::set<QPDFObjGen> added;
679 for (auto field: foreign_afdh.getFormFieldsForPage(foreign_page)) 680 for (auto field: foreign_afdh.getFormFieldsForPage(foreign_page))
680 { 681 {
681 auto new_field = this->qpdf.copyForeignObject( 682 auto new_field = this->qpdf.copyForeignObject(
682 field.getObjectHandle()); 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,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 static void do_under_overlay_for_page( 5159 static void do_under_overlay_for_page(
5147 QPDF& pdf, 5160 QPDF& pdf,
5148 Options& o, 5161 Options& o,
@@ -5164,12 +5177,7 @@ static void do_under_overlay_for_page( @@ -5164,12 +5177,7 @@ static void do_under_overlay_for_page(
5164 PointerHolder<QPDFAcroFormDocumentHelper>> afdh; 5177 PointerHolder<QPDFAcroFormDocumentHelper>> afdh;
5165 auto make_afdh = [&](QPDFPageObjectHelper& ph) { 5178 auto make_afdh = [&](QPDFPageObjectHelper& ph) {
5166 QPDF* q = ph.getObjectHandle().getOwningQPDF(); 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 auto dest_afdh = make_afdh(dest_page); 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,6 +5843,9 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
5835 std::vector<QPDFObjectHandle> new_labels; 5843 std::vector<QPDFObjectHandle> new_labels;
5836 bool any_page_labels = false; 5844 bool any_page_labels = false;
5837 int out_pageno = 0; 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 for (std::vector<QPDFPageData>::iterator iter = 5849 for (std::vector<QPDFPageData>::iterator iter =
5839 parsed_specs.begin(); 5850 parsed_specs.begin();
5840 iter != parsed_specs.end(); ++iter) 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,6 +5858,7 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
5847 cis->stayOpen(true); 5858 cis->stayOpen(true);
5848 } 5859 }
5849 QPDFPageLabelDocumentHelper pldh(*page_data.qpdf); 5860 QPDFPageLabelDocumentHelper pldh(*page_data.qpdf);
  5861 + auto other_afdh = get_afdh_for_qpdf(afdh_map, page_data.qpdf);
5850 if (pldh.hasPageLabels()) 5862 if (pldh.hasPageLabels())
5851 { 5863 {
5852 any_page_labels = true; 5864 any_page_labels = true;
@@ -5891,6 +5903,11 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings) @@ -5891,6 +5903,11 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
5891 // of the fact that we are using it. 5903 // of the fact that we are using it.
5892 selected_from_orig.insert(pageno); 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 if (page_data.qpdf->anyWarnings()) 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,6 +6286,7 @@ static void do_split_pages(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
6269 dh.removeUnreferencedResources(); 6286 dh.removeUnreferencedResources();
6270 } 6287 }
6271 QPDFPageLabelDocumentHelper pldh(pdf); 6288 QPDFPageLabelDocumentHelper pldh(pdf);
  6289 + QPDFAcroFormDocumentHelper afdh(pdf);
6272 std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages(); 6290 std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
6273 size_t pageno_len = QUtil::uint_to_string(pages.size()).length(); 6291 size_t pageno_len = QUtil::uint_to_string(pages.size()).length();
6274 size_t num_pages = pages.size(); 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,6 +6300,11 @@ static void do_split_pages(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
6282 } 6300 }
6283 QPDF outpdf; 6301 QPDF outpdf;
6284 outpdf.emptyPDF(); 6302 outpdf.emptyPDF();
  6303 + PointerHolder<QPDFAcroFormDocumentHelper> out_afdh;
  6304 + if (afdh.hasAcroForm())
  6305 + {
  6306 + out_afdh = new QPDFAcroFormDocumentHelper(outpdf);
  6307 + }
6285 if (o.suppress_warnings) 6308 if (o.suppress_warnings)
6286 { 6309 {
6287 outpdf.setSuppressWarnings(true); 6310 outpdf.setSuppressWarnings(true);
@@ -6290,6 +6313,12 @@ static void do_split_pages(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings) @@ -6290,6 +6313,12 @@ static void do_split_pages(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
6290 { 6313 {
6291 QPDFObjectHandle page = pages.at(pageno - 1); 6314 QPDFObjectHandle page = pages.at(pageno - 1);
6292 outpdf.addPage(page, false); 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 if (pldh.hasPageLabels()) 6323 if (pldh.hasPageLabels())
6295 { 6324 {
qpdf/qpdf.testcov
@@ -575,3 +575,5 @@ QPDFPageObjectHelper flatten inherit rotate 0 @@ -575,3 +575,5 @@ QPDFPageObjectHelper flatten inherit rotate 0
575 QPDFAcroFormDocumentHelper copy annotation 3 575 QPDFAcroFormDocumentHelper copy annotation 3
576 QPDFAcroFormDocumentHelper field with parent 3 576 QPDFAcroFormDocumentHelper field with parent 3
577 QPDFAcroFormDocumentHelper modify ap matrix 0 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,7 +2414,7 @@ foreach my $f (qw(screen print))
2414 show_ntests(); 2414 show_ntests();
2415 # ---------- 2415 # ----------
2416 $td->notify("--- Copy Annotations ---"); 2416 $td->notify("--- Copy Annotations ---");
2417 -$n_tests += 16; 2417 +$n_tests += 21;
2418 2418
2419 $td->runtest("complex copy annotations", 2419 $td->runtest("complex copy annotations",
2420 {$td->COMMAND => 2420 {$td->COMMAND =>
@@ -2458,6 +2458,28 @@ foreach my $d ([1, &quot;appearances-1.pdf&quot;], @@ -2458,6 +2458,28 @@ foreach my $d ([1, &quot;appearances-1.pdf&quot;],
2458 {$td->FILE => "test80b$n.pdf"}); 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 show_ntests(); 2483 show_ntests();
2462 # ---------- 2484 # ----------
2463 $td->notify("--- Page Tree Issues ---"); 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