From dfce581754142d37308313b6788598771f242039 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Wed, 17 Feb 2021 20:04:55 -0500 Subject: [PATCH] Add numeric argument to --collate This takes pages from the file in groups of n with default = 1. This partially fixes the enhancement in issue #505 but doesn't implement the entire suggestion. --- ChangeLog | 6 + manual/qpdf-manual.xml | 37 +- qpdf/qpdf.cc | 36 +- qpdf/qtest/qpdf.test | 15 +- qpdf/qtest/qpdf/three-files-2-collate-out.pdf | 978 ++++++++++++++++++ 5 files changed, 1051 insertions(+), 21 deletions(-) create mode 100644 qpdf/qtest/qpdf/three-files-2-collate-out.pdf diff --git a/ChangeLog b/ChangeLog index 2009162c..676a2466 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2021-02-17 Jay Berkenbilt + + * Allow optional numeric argument to --collate. If --collate=n is + given, pull n pages from the first file, n pages from the second + file, etc., until we run out of pages. + 2021-02-15 Jay Berkenbilt * Add a version of QPDFObjectHandle::parse that takes a QPDF* as diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml index 508e0ec9..144c4edf 100644 --- a/manual/qpdf-manual.xml +++ b/manual/qpdf-manual.xml @@ -1077,12 +1077,14 @@ make - + When specified, collate rather than concatenate pages from - files specified with . See for additional details. + files specified with . With a numeric + argument, collate in groups of n. + The default is 1. See for + additional details. @@ -1646,6 +1648,27 @@ make a.pdf page 5 + + Starting in qpdf version 10.2, you may specify a numeric argument + to . With + , pull + groups of n pages from each file, + again, stopping when there are no more pages. For example, if you + ran qpdf --collate=2 --empty --pages a.pdf 1-5 b.pdf 6-4 + c.pdf r1 -- out.pdf, you would get the following pages + in this order: + + a.pdf page 1 + a.pdf page 2 + b.pdf page 6 + b.pdf page 5 + c.pdf last page + a.pdf page 3 + a.pdf page 4 + b.pdf page 4 + a.pdf page 5 + + Starting in qpdf version 8.3, when you split and merge files, any page labels (page numbers) are preserved in the final file. It is @@ -5144,6 +5167,14 @@ print "\n"; reference to the file spec object. + + + Add numeric option to . If + is + given, take pages in groups of n + from the given files. + + diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index e41be64b..8de2ab8b 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -201,7 +201,7 @@ struct Options show_filtered_stream_data(false), show_pages(false), show_page_images(false), - collate(false), + collate(0), flatten_rotation(false), list_attachments(false), json(false), @@ -307,7 +307,7 @@ struct Options bool show_filtered_stream_data; bool show_pages; bool show_page_images; - bool collate; + size_t collate; bool flatten_rotation; bool list_attachments; std::string attachment_to_show; @@ -804,7 +804,7 @@ class ArgParser void argUnderlay(); void argOverlay(); void argRotate(char* parameter); - void argCollate(); + void argCollate(char* parameter); void argFlattenRotation(); void argListAttachments(); void argShowAttachment(char* parameter); @@ -1048,7 +1048,7 @@ ArgParser::initOptionTable() &ArgParser::argRotate, "[+|-]angle:page-range"); char const* stream_data_choices[] = {"compress", "preserve", "uncompress", 0}; - (*t)["collate"] = oe_bare(&ArgParser::argCollate); + (*t)["collate"] = oe_optionalParameter(&ArgParser::argCollate); (*t)["flatten-rotation"] = oe_bare(&ArgParser::argFlattenRotation); (*t)["list-attachments"] = oe_bare(&ArgParser::argListAttachments); (*t)["show-attachment"] = oe_requiredParameter( @@ -1369,8 +1369,9 @@ ArgParser::argHelp() << " encoding errors\n" << "--password-mode=mode control qpdf's encoding of passwords\n" << "--pages options -- select specific pages from one or more files\n" - << "--collate causes files specified in --pages to be collated\n" - << " rather than concatenated\n" + << "--collate=n causes files specified in --pages to be collated\n" + << " in groups of n pages (default 1) rather than\n" + << " concatenated\n" << "--flatten-rotation move page rotation from /Rotate key to content\n" << "--rotate=[+|-]angle[:page-range]\n" << " rotate each specified page 90, 180, or 270 degrees;\n" @@ -2068,9 +2069,11 @@ ArgParser::argEncryptionFilePassword(char* parameter) } void -ArgParser::argCollate() +ArgParser::argCollate(char* parameter) { - o.collate = true; + auto n = ((parameter == 0) ? 1 : + QUtil::string_to_uint(parameter)); + o.collate = QIntC::to_size(n); } void @@ -5788,16 +5791,19 @@ static void handle_page_specs(QPDF& pdf, Options& o, bool& warnings) for (size_t i = 0; i < nspecs; ++i) { QPDFPageData& page_data = parsed_specs.at(i); - if (cur_page < page_data.selected_pages.size()) + for (size_t j = 0; j < o.collate; ++j) { - got_pages = true; - new_parsed_specs.push_back( - QPDFPageData( - page_data, - page_data.selected_pages.at(cur_page))); + if (cur_page + j < page_data.selected_pages.size()) + { + got_pages = true; + new_parsed_specs.push_back( + QPDFPageData( + page_data, + page_data.selected_pages.at(cur_page + j))); + } } } - ++cur_page; + cur_page += o.collate; } parsed_specs = new_parsed_specs; } diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 38a2d1b8..c033e7ef 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -2573,17 +2573,26 @@ show_ntests(); # ---------- $td->notify("--- Collating ---"); my @collate = ( - ["three-files", "collate-odd", + ["", "three-files", "collate-odd", + "collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"], + [1, "three-files", "collate-odd", + "collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"], + [2, "three-files-2", "collate-odd", "collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"], ); $n_tests += 2 * scalar(@collate); foreach my $d (@collate) { - my ($description, $first, $args) = @$d; + my ($n, $description, $first, $args) = @$d; + my $collate = '--collate'; + if ($n) + { + $collate .= "=$n"; + } $td->runtest("collate pages: $description", {$td->COMMAND => - "qpdf --qdf --static-id --collate $first.pdf" . + "qpdf --qdf --static-id $collate $first.pdf" . " --pages $args -- a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", diff --git a/qpdf/qtest/qpdf/three-files-2-collate-out.pdf b/qpdf/qtest/qpdf/three-files-2-collate-out.pdf new file mode 100644 index 00000000..2abda2da --- /dev/null +++ b/qpdf/qtest/qpdf/three-files-2-collate-out.pdf @@ -0,0 +1,978 @@ +%PDF-1.3 +%¿÷¢þ +%QDF-1.0 + +%% Original object ID: 1 0 +1 0 obj +<< + /Outlines 2 0 R + /PageLabels << + /Nums [ + 0 + << + /P () + /St 1 + >> + 1 + << + /S /r + /St 1 + >> + 2 + << + /St 3 + >> + 3 + << + /S /D + /St 3 + >> + 4 + << + /P () + /St 1 + >> + 5 + << + /S /r + /St 3 + >> + 6 + << + /S /r + /St 5 + >> + 8 + << + /P () + /St 1 + >> + 10 + << + /S /r + /St 4 + >> + 11 + << + /S /r + /St 2 + >> + 12 + << + /P () + /St 2 + >> + ] + >> + /PageMode /UseOutlines + /Pages 3 0 R + /Type /Catalog +>> +endobj + +%% Original object ID: 2 0 +2 0 obj +<< + /Count 6 + /First 4 0 R + /Last 5 0 R + /Type /Outlines +>> +endobj + +%% Original object ID: 3 0 +3 0 obj +<< + /Count 13 + /Kids [ + 6 0 R + 7 0 R + 8 0 R + 9 0 R + 10 0 R + 11 0 R + 12 0 R + 13 0 R + 14 0 R + 15 0 R + 16 0 R + 17 0 R + 18 0 R + ] + /Type /Pages +>> +endobj + +%% Original object ID: 4 0 +4 0 obj +<< + /Count 4 + /Dest [ + null + /XYZ + null + null + null + ] + /First 19 0 R + /Last 20 0 R + /Next 5 0 R + /Parent 2 0 R + /Title (Isís 1 -> 5: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 5 0 +5 0 obj +<< + /Dest [ + null + /XYZ + 66 + 756 + 3 + ] + /Parent 2 0 R + /Prev 4 0 R + /Title (Trepak 2 -> 15: /XYZ 66 756 3) + /Type /Outline +>> +endobj + +%% Page 1 +%% Original object ID: 6 0 +6 0 obj +<< + /Contents 21 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Page 2 +%% Original object ID: 7 0 +7 0 obj +<< + /Contents 25 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Page 3 +%% Original object ID: 47 0 +8 0 obj +<< + /Contents 27 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 29 0 R + >> + /ProcSet 30 0 R + >> + /Type /Page +>> +endobj + +%% Page 4 +%% Original object ID: 51 0 +9 0 obj +<< + /Contents 31 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 5 +%% Original object ID: 55 0 +10 0 obj +<< + /Contents 35 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 6 +%% Original object ID: 8 0 +11 0 obj +<< + /Contents 37 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Page 7 +%% Original object ID: 9 0 +12 0 obj +<< + /Contents 39 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Page 8 +%% Original object ID: 57 0 +13 0 obj +<< + /Contents 41 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 9 +%% Original object ID: 59 0 +14 0 obj +<< + /Contents 43 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 10 +%% Original object ID: 10 0 +15 0 obj +<< + /Contents 45 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 23 0 R + >> + /ProcSet 24 0 R + >> + /Type /Page +>> +endobj + +%% Page 11 +%% Original object ID: 61 0 +16 0 obj +<< + /Contents 47 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 12 +%% Original object ID: 63 0 +17 0 obj +<< + /Contents 49 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Page 13 +%% Original object ID: 65 0 +18 0 obj +<< + /Contents 51 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources << + /Font << + /F1 33 0 R + >> + /ProcSet 34 0 R + >> + /Type /Page +>> +endobj + +%% Original object ID: 21 0 +19 0 obj +<< + /Count -3 + /Dest [ + null + /Fit + ] + /First 53 0 R + /Last 54 0 R + /Next 20 0 R + /Parent 4 0 R + /Title (Amanda 1.1 -> 11: /Fit) + /Type /Outline +>> +endobj + +%% Original object ID: 22 0 +20 0 obj +<< + /Count 2 + /Dest [ + null + /FitH + 792 + ] + /First 55 0 R + /Last 56 0 R + /Parent 4 0 R + /Prev 19 0 R + /Title + /Type /Outline +>> +endobj + +%% Contents for page 1 +%% Original object ID: 23 0 +21 0 obj +<< + /Length 22 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 0) Tj +ET +endstream +endobj + +22 0 obj +46 +endobj + +%% Original object ID: 24 0 +23 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +%% Original object ID: 25 0 +24 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 2 +%% Original object ID: 26 0 +25 0 obj +<< + /Length 26 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 2) Tj +ET +endstream +endobj + +26 0 obj +46 +endobj + +%% Contents for page 3 +%% Original object ID: 48 0 +27 0 obj +<< + /Length 28 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj + +28 0 obj +44 +endobj + +%% Original object ID: 49 0 +29 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +%% Original object ID: 50 0 +30 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 4 +%% Original object ID: 52 0 +31 0 obj +<< + /Length 32 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 13) Tj +ET +endstream +endobj + +32 0 obj +47 +endobj + +%% Original object ID: 53 0 +33 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +%% Original object ID: 54 0 +34 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 5 +%% Original object ID: 56 0 +35 0 obj +<< + /Length 36 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 11) Tj +ET +endstream +endobj + +36 0 obj +47 +endobj + +%% Contents for page 6 +%% Original object ID: 27 0 +37 0 obj +<< + /Length 38 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 4) Tj +ET +endstream +endobj + +38 0 obj +46 +endobj + +%% Contents for page 7 +%% Original object ID: 28 0 +39 0 obj +<< + /Length 40 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 6) Tj +ET +endstream +endobj + +40 0 obj +46 +endobj + +%% Contents for page 8 +%% Original object ID: 58 0 +41 0 obj +<< + /Length 42 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 9) Tj +ET +endstream +endobj + +42 0 obj +46 +endobj + +%% Contents for page 9 +%% Original object ID: 60 0 +43 0 obj +<< + /Length 44 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 7) Tj +ET +endstream +endobj + +44 0 obj +46 +endobj + +%% Contents for page 10 +%% Original object ID: 29 0 +45 0 obj +<< + /Length 46 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 8) Tj +ET +endstream +endobj + +46 0 obj +46 +endobj + +%% Contents for page 11 +%% Original object ID: 62 0 +47 0 obj +<< + /Length 48 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 5) Tj +ET +endstream +endobj + +48 0 obj +46 +endobj + +%% Contents for page 12 +%% Original object ID: 64 0 +49 0 obj +<< + /Length 50 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 3) Tj +ET +endstream +endobj + +50 0 obj +46 +endobj + +%% Contents for page 13 +%% Original object ID: 66 0 +51 0 obj +<< + /Length 52 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 1) Tj +ET +endstream +endobj + +52 0 obj +46 +endobj + +%% Original object ID: 40 0 +53 0 obj +<< + /Count -2 + /Dest [ + 57 0 R + /FitV + 100 + ] + /First 58 0 R + /Last 59 0 R + /Next 54 0 R + /Parent 19 0 R + /Title (Isosicle 1.1.1 -> 12: /FitV 100) + /Type /Outline +>> +endobj + +%% Original object ID: 41 0 +54 0 obj +<< + /Count 1 + /Dest [ + 57 0 R + /XYZ + null + null + null + ] + /First 60 0 R + /Last 60 0 R + /Parent 19 0 R + /Prev 53 0 R + /Title (Isosicle 1.1.2 -> 12: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 42 0 +55 0 obj +<< + /Dest [ + null + /FitR + 66 + 714 + 180 + 770 + ] + /Next 56 0 R + /Parent 20 0 R + /Title (Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770) + /Type /Outline +>> +endobj + +%% Original object ID: 43 0 +56 0 obj +<< + /Dest [ + 6 0 R + /XYZ + null + null + null + ] + /Parent 20 0 R + /Prev 55 0 R + /Title (Trepsicle 1.2.2 -> 0: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 12 0 +57 0 obj +null +endobj + +%% Original object ID: 44 0 +58 0 obj +<< + /Dest [ + 61 0 R + /XYZ + null + null + null + ] + /Next 59 0 R + /Parent 53 0 R + /Title (Isosicle 1.1.1.1 -> 18: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 45 0 +59 0 obj +<< + /Dest [ + null + /XYZ + null + null + null + ] + /Parent 53 0 R + /Prev 58 0 R + /Title (Isosicle 1.1.1.2 -> 19: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 46 0 +60 0 obj +<< + /Dest [ + 62 0 R + /XYZ + null + null + null + ] + /Parent 54 0 R + /Title (Isosicle 1.1.2.1 -> 22: /XYZ null null null) + /Type /Outline +>> +endobj + +%% Original object ID: 15 0 +61 0 obj +null +endobj + +%% Original object ID: 17 0 +62 0 obj +null +endobj + +xref +0 63 +0000000000 65535 f +0000000052 00000 n +0000000799 00000 n +0000000906 00000 n +0000001135 00000 n +0000001376 00000 n +0000001576 00000 n +0000001808 00000 n +0000002041 00000 n +0000002274 00000 n +0000002507 00000 n +0000002740 00000 n +0000002973 00000 n +0000003207 00000 n +0000003441 00000 n +0000003676 00000 n +0000003911 00000 n +0000004146 00000 n +0000004381 00000 n +0000004605 00000 n +0000004813 00000 n +0000005170 00000 n +0000005273 00000 n +0000005321 00000 n +0000005468 00000 n +0000005555 00000 n +0000005658 00000 n +0000005729 00000 n +0000005830 00000 n +0000005878 00000 n +0000006025 00000 n +0000006112 00000 n +0000006216 00000 n +0000006264 00000 n +0000006411 00000 n +0000006498 00000 n +0000006602 00000 n +0000006673 00000 n +0000006776 00000 n +0000006847 00000 n +0000006950 00000 n +0000007021 00000 n +0000007124 00000 n +0000007195 00000 n +0000007298 00000 n +0000007370 00000 n +0000007473 00000 n +0000007545 00000 n +0000007648 00000 n +0000007720 00000 n +0000007823 00000 n +0000007895 00000 n +0000007998 00000 n +0000008046 00000 n +0000008275 00000 n +0000008531 00000 n +0000008751 00000 n +0000008964 00000 n +0000009014 00000 n +0000009230 00000 n +0000009444 00000 n +0000009645 00000 n +0000009695 00000 n +trailer << + /Root 1 0 R + /Size 63 + /ID [<31415926535897932384626433832795>] +>> +startxref +9717 +%%EOF