Commit 83216e640c489a22bd4001c9c39affb72b8b1124
1 parent
1f35ec99
Preserve form fields when splitting pages (fixes #340)
Showing
9 changed files
with
70 additions
and
8 deletions
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& uo, | @@ -5143,6 +5143,19 @@ static void get_uo_pagenos(UnderOverlay& 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& pdf, Options& o, bool& warnings) | @@ -5835,6 +5843,9 @@ static void handle_page_specs(QPDF& pdf, Options& o, bool& 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& pdf, Options& o, bool& warnings) | @@ -5847,6 +5858,7 @@ static void handle_page_specs(QPDF& pdf, Options& o, bool& 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& pdf, Options& o, bool& warnings) | @@ -5891,6 +5903,11 @@ static void handle_page_specs(QPDF& pdf, Options& o, bool& 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& pdf, Options& o, bool& warnings) | @@ -6269,6 +6286,7 @@ static void do_split_pages(QPDF& pdf, Options& o, bool& 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& pdf, Options& o, bool& warnings) | @@ -6282,6 +6300,11 @@ static void do_split_pages(QPDF& pdf, Options& o, bool& 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& pdf, Options& o, bool& warnings) | @@ -6290,6 +6313,12 @@ static void do_split_pages(QPDF& pdf, Options& o, bool& 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, "appearances-1.pdf"], | @@ -2458,6 +2458,28 @@ foreach my $d ([1, "appearances-1.pdf"], | ||
| 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