Commit 2f232e8efbb9d8eaa4bd34d0b4de2ad557d3200c

Authored by m-holger
Committed by GitHub
2 parents 6d42b9a7 1a000fce

Merge pull request #1204 from m-holger/assemble

Refactor QPDFJob::handlePageSpecs
include/qpdf/QPDFJob.hh
... ... @@ -156,16 +156,6 @@ class QPDFJob
156 156 bool replace{false};
157 157 };
158 158  
159   - struct PageSpec
160   - {
161   - PageSpec(
162   - std::string const& filename, std::string const& password, std::string const& range);
163   -
164   - std::string filename;
165   - std::string password;
166   - std::string range;
167   - };
168   -
169 159 public:
170 160 // CONFIGURATION
171 161  
... ... @@ -425,58 +415,16 @@ class QPDFJob
425 415 [[deprecated("use job_json_schema(version)")]] static std::string QPDF_DLL job_json_schema_v1();
426 416  
427 417 private:
428   - struct RotationSpec
429   - {
430   - RotationSpec(int angle = 0, bool relative = false) :
431   - angle(angle),
432   - relative(relative)
433   - {
434   - }
435   -
436   - int angle;
437   - bool relative;
438   - };
  418 + struct PageNo;
  419 + struct Selection;
  420 + struct Input;
  421 + struct Inputs;
  422 + struct RotationSpec;
  423 + struct UnderOverlay;
  424 + struct PageLabelSpec;
439 425  
440 426 enum password_mode_e { pm_bytes, pm_hex_bytes, pm_unicode, pm_auto };
441 427  
442   - struct UnderOverlay
443   - {
444   - UnderOverlay(char const* which) :
445   - which(which),
446   - to_nr("1-z"),
447   - from_nr("1-z"),
448   - repeat_nr("")
449   - {
450   - }
451   -
452   - std::string which;
453   - std::string filename;
454   - std::string password;
455   - std::string to_nr;
456   - std::string from_nr;
457   - std::string repeat_nr;
458   - std::unique_ptr<QPDF> pdf;
459   - std::vector<int> to_pagenos;
460   - std::vector<int> from_pagenos;
461   - std::vector<int> repeat_pagenos;
462   - };
463   -
464   - struct PageLabelSpec
465   - {
466   - PageLabelSpec(
467   - int first_page, qpdf_page_label_e label_type, int start_num, std::string_view prefix) :
468   - first_page(first_page),
469   - label_type(label_type),
470   - start_num(start_num),
471   - prefix(prefix)
472   - {
473   - }
474   - int first_page;
475   - qpdf_page_label_e label_type;
476   - int start_num{1};
477   - std::string prefix;
478   - };
479   -
480 428 // Helper functions
481 429 static void usage(std::string const& msg);
482 430 static JSON json_schema(int json_version, std::set<std::string>* keys = nullptr);
... ... @@ -513,20 +461,19 @@ class QPDFJob
513 461  
514 462 // Transformations
515 463 void setQPDFOptions(QPDF& pdf);
516   - void handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_heap);
  464 + void handlePageSpecs(QPDF& pdf);
517 465 bool shouldRemoveUnreferencedResources(QPDF& pdf);
518 466 void handleRotations(QPDF& pdf);
519 467 void getUOPagenos(
520   - std::vector<UnderOverlay>& uo, std::map<int, std::map<size_t, std::vector<int>>>& pagenos);
  468 + std::vector<UnderOverlay>& uo, std::vector<std::map<size_t, std::vector<int>>>& pagenos);
521 469 void handleUnderOverlay(QPDF& pdf);
522 470 std::string doUnderOverlayForPage(
523 471 QPDF& pdf,
524 472 UnderOverlay& uo,
525   - std::map<int, std::map<size_t, std::vector<int>>>& pagenos,
526   - size_t page_idx,
  473 + std::vector<std::map<size_t, std::vector<int>>>& pagenos,
  474 + PageNo const& page_idx,
527 475 size_t uo_idx,
528 476 std::map<int, std::map<size_t, QPDFObjectHandle>>& fo,
529   - std::vector<QPDFPageObjectHelper>& pages,
530 477 QPDFPageObjectHelper& dest_page);
531 478 void validateUnderOverlay(QPDF& pdf, UnderOverlay* uo);
532 479 void handleTransformations(QPDF& pdf);
... ... @@ -568,159 +515,8 @@ class QPDFJob
568 515  
569 516 enum remove_unref_e { re_auto, re_yes, re_no };
570 517  
571   - class Members
572   - {
573   - friend class QPDFJob;
574   -
575   - public:
576   - QPDF_DLL
577   - ~Members() = default;
  518 + class Members;
578 519  
579   - private:
580   - // These default values are duplicated in help and docs.
581   - static int constexpr DEFAULT_KEEP_FILES_OPEN_THRESHOLD = 200;
582   - static int constexpr DEFAULT_OI_MIN_WIDTH = 128;
583   - static int constexpr DEFAULT_OI_MIN_HEIGHT = 128;
584   - static int constexpr DEFAULT_OI_MIN_AREA = 16384;
585   - static int constexpr DEFAULT_II_MIN_BYTES = 1024;
586   -
587   - Members();
588   - Members(Members const&) = delete;
589   -
590   - std::shared_ptr<QPDFLogger> log;
591   - std::string message_prefix{"qpdf"};
592   - bool warnings{false};
593   - unsigned long encryption_status{0};
594   - bool verbose{false};
595   - std::string password;
596   - bool linearize{false};
597   - bool decrypt{false};
598   - bool remove_restrictions{false};
599   - int split_pages{0};
600   - bool progress{false};
601   - std::function<void(int)> progress_handler{nullptr};
602   - bool suppress_warnings{false};
603   - bool warnings_exit_zero{false};
604   - bool copy_encryption{false};
605   - std::string encryption_file;
606   - std::string encryption_file_password;
607   - bool encrypt{false};
608   - bool password_is_hex_key{false};
609   - bool suppress_password_recovery{false};
610   - password_mode_e password_mode{pm_auto};
611   - bool allow_insecure{false};
612   - bool allow_weak_crypto{false};
613   - std::string user_password;
614   - std::string owner_password;
615   - int keylen{0};
616   - bool r2_print{true};
617   - bool r2_modify{true};
618   - bool r2_extract{true};
619   - bool r2_annotate{true};
620   - bool r3_accessibility{true};
621   - bool r3_extract{true};
622   - bool r3_assemble{true};
623   - bool r3_annotate_and_form{true};
624   - bool r3_form_filling{true};
625   - bool r3_modify_other{true};
626   - qpdf_r3_print_e r3_print{qpdf_r3p_full};
627   - bool force_V4{false};
628   - bool force_R5{false};
629   - bool cleartext_metadata{false};
630   - bool use_aes{false};
631   - bool stream_data_set{false};
632   - qpdf_stream_data_e stream_data_mode{qpdf_s_compress};
633   - bool compress_streams{true};
634   - bool compress_streams_set{false};
635   - bool recompress_flate{false};
636   - bool recompress_flate_set{false};
637   - int compression_level{-1};
638   - int jpeg_quality{-1};
639   - qpdf_stream_decode_level_e decode_level{qpdf_dl_generalized};
640   - bool decode_level_set{false};
641   - bool normalize_set{false};
642   - bool normalize{false};
643   - bool suppress_recovery{false};
644   - bool object_stream_set{false};
645   - qpdf_object_stream_e object_stream_mode{qpdf_o_preserve};
646   - bool ignore_xref_streams{false};
647   - bool qdf_mode{false};
648   - bool preserve_unreferenced_objects{false};
649   - remove_unref_e remove_unreferenced_page_resources{re_auto};
650   - bool keep_files_open{true};
651   - bool keep_files_open_set{false};
652   - size_t keep_files_open_threshold{DEFAULT_KEEP_FILES_OPEN_THRESHOLD};
653   - bool newline_before_endstream{false};
654   - std::string linearize_pass1;
655   - bool coalesce_contents{false};
656   - bool flatten_annotations{false};
657   - int flatten_annotations_required{0};
658   - int flatten_annotations_forbidden{an_invisible | an_hidden};
659   - bool generate_appearances{false};
660   - PDFVersion max_input_version;
661   - std::string min_version;
662   - std::string force_version;
663   - bool show_npages{false};
664   - bool deterministic_id{false};
665   - bool static_id{false};
666   - bool static_aes_iv{false};
667   - bool suppress_original_object_id{false};
668   - bool show_encryption{false};
669   - bool show_encryption_key{false};
670   - bool check_linearization{false};
671   - bool show_linearization{false};
672   - bool show_xref{false};
673   - bool show_trailer{false};
674   - int show_obj{0};
675   - int show_gen{0};
676   - bool show_raw_stream_data{false};
677   - bool show_filtered_stream_data{false};
678   - bool show_pages{false};
679   - bool show_page_images{false};
680   - std::vector<size_t> collate;
681   - bool flatten_rotation{false};
682   - bool list_attachments{false};
683   - std::string attachment_to_show;
684   - std::list<std::string> attachments_to_remove;
685   - std::list<AddAttachment> attachments_to_add;
686   - std::list<CopyAttachmentFrom> attachments_to_copy;
687   - int json_version{0};
688   - std::set<std::string> json_keys;
689   - std::set<std::string> json_objects;
690   - qpdf_json_stream_data_e json_stream_data{qpdf_sj_none};
691   - bool json_stream_data_set{false};
692   - std::string json_stream_prefix;
693   - bool test_json_schema{false};
694   - bool check{false};
695   - bool optimize_images{false};
696   - bool externalize_inline_images{false};
697   - bool keep_inline_images{false};
698   - bool remove_info{false};
699   - bool remove_metadata{false};
700   - bool remove_page_labels{false};
701   - bool remove_structure{false};
702   - size_t oi_min_width{DEFAULT_OI_MIN_WIDTH};
703   - size_t oi_min_height{DEFAULT_OI_MIN_HEIGHT};
704   - size_t oi_min_area{DEFAULT_OI_MIN_AREA};
705   - size_t ii_min_bytes{DEFAULT_II_MIN_BYTES};
706   - std::vector<UnderOverlay> underlay;
707   - std::vector<UnderOverlay> overlay;
708   - UnderOverlay* under_overlay{nullptr};
709   - std::vector<PageSpec> page_specs;
710   - std::map<std::string, RotationSpec> rotations;
711   - bool require_outfile{true};
712   - bool replace_input{false};
713   - bool check_is_encrypted{false};
714   - bool check_requires_password{false};
715   - std::string infilename;
716   - bool empty_input{false};
717   - std::string outfilename;
718   - bool json_input{false};
719   - bool json_output{false};
720   - std::string update_from_json;
721   - bool report_mem_usage{false};
722   - std::vector<PageLabelSpec> page_label_specs;
723   - };
724 520 std::shared_ptr<Members> m;
725 521 };
726 522  
... ...
libqpdf/QPDFJob.cc
1   -#include <qpdf/QPDFJob.hh>
  1 +#include <qpdf/QPDFJob_private.hh>
2 2  
3 3 #include <cstring>
4 4 #include <iostream>
... ... @@ -58,18 +58,7 @@ namespace
58 58 std::shared_ptr<Pl_DCT::CompressConfig> config;
59 59 };
60 60  
61   - struct QPDFPageData
62   - {
63   - QPDFPageData(std::string const& filename, QPDF* qpdf, std::string const& range);
64   - QPDFPageData(QPDFPageData const& other, int page);
65   -
66   - std::string filename;
67   - QPDF* qpdf;
68   - std::vector<QPDFObjectHandle> orig_pages;
69   - std::vector<int> selected_pages;
70   - };
71   -
72   - class ProgressReporter: public QPDFWriter::ProgressReporter
  61 + class ProgressReporter final: public QPDFWriter::ProgressReporter
73 62 {
74 63 public:
75 64 ProgressReporter(Pipeline& p, std::string const& prefix, char const* filename) :
... ... @@ -78,8 +67,12 @@ namespace
78 67 filename(filename)
79 68 {
80 69 }
81   - ~ProgressReporter() override = default;
82   - void reportProgress(int) override;
  70 + ~ProgressReporter() final = default;
  71 + void
  72 + reportProgress(int percentage) final
  73 + {
  74 + p << prefix << ": " << filename << ": write progress: " << percentage << "%\n";
  75 + }
83 76  
84 77 private:
85 78 Pipeline& p;
... ... @@ -239,47 +232,32 @@ ImageOptimizer::provideStreamData(QPDFObjGen const&amp;, Pipeline* pipeline)
239 232 image.pipeStreamData(p.get(), 0, decode_level, false, false);
240 233 }
241 234  
242   -QPDFJob::PageSpec::PageSpec(
243   - std::string const& filename, std::string const& password, std::string const& range) :
244   - filename(filename),
245   - password(password.empty() ? "" : password),
246   - range(range)
  235 +// Page number (1 based) and index (0 based). Defaults to page number 1 / index 0.
  236 +struct QPDFJob::PageNo
247 237 {
248   -}
  238 + PageNo() = default;
  239 + PageNo(PageNo const&) = default;
249 240  
250   -QPDFPageData::QPDFPageData(std::string const& filename, QPDF* qpdf, std::string const& range) :
251   - filename(filename),
252   - qpdf(qpdf),
253   - orig_pages(qpdf->getAllPages())
254   -{
255   - try {
256   - selected_pages = QUtil::parse_numrange(range.c_str(), QIntC::to_int(orig_pages.size()));
257   - } catch (std::runtime_error& e) {
258   - throw std::runtime_error("parsing numeric range for " + filename + ": " + e.what());
  241 + PageNo(int no) :
  242 + idx{QIntC::to_size(no - 1)},
  243 + no{no}
  244 + {
259 245 }
260   -}
261 246  
262   -QPDFPageData::QPDFPageData(QPDFPageData const& other, int page) :
263   - filename(other.filename),
264   - qpdf(other.qpdf),
265   - orig_pages(other.orig_pages)
266   -{
267   - selected_pages.push_back(page);
268   -}
269   -
270   -void
271   -ProgressReporter::reportProgress(int percentage)
272   -{
273   - p << prefix << ": " << filename << ": write progress: " << percentage << "%\n";
274   -}
  247 + PageNo&
  248 + operator++()
  249 + {
  250 + ++idx;
  251 + ++no;
  252 + return *this;
  253 + }
275 254  
276   -QPDFJob::Members::Members() :
277   - log(QPDFLogger::defaultLogger())
278   -{
279   -}
  255 + size_t idx{0};
  256 + int no{1};
  257 +};
280 258  
281 259 QPDFJob::QPDFJob() :
282   - m(new Members())
  260 + m(std::make_shared<Members>(*this))
283 261 {
284 262 }
285 263  
... ... @@ -416,7 +394,7 @@ QPDFJob::createQPDF()
416 394 checkConfiguration();
417 395 std::unique_ptr<QPDF> pdf_sp;
418 396 try {
419   - processFile(pdf_sp, m->infilename.data(), m->password.data(), true, true);
  397 + processFile(pdf_sp, m->infile_nm(), m->password.data(), true, true);
420 398 } catch (QPDFExc& e) {
421 399 if (e.getErrorCode() == qpdf_e_password) {
422 400 // Allow certain operations to work when an incorrect password is supplied.
... ... @@ -447,40 +425,34 @@ QPDFJob::createQPDF()
447 425 pdf.updateFromJSON(m->update_from_json);
448 426 }
449 427  
450   - std::vector<std::unique_ptr<QPDF>> page_heap;
451   - if (!m->page_specs.empty()) {
452   - handlePageSpecs(pdf, page_heap);
453   - }
  428 + handlePageSpecs(pdf);
454 429 if (!m->rotations.empty()) {
455 430 handleRotations(pdf);
456 431 }
457 432 handleUnderOverlay(pdf);
458 433 handleTransformations(pdf);
  434 + m->warnings |= m->inputs.clear();
  435 +
  436 + auto root = pdf.getRoot();
459 437 if (m->remove_info) {
460 438 auto trailer = pdf.getTrailer();
461   - auto mod_date = trailer.getKey("/Info").getKeyIfDict("/ModDate");
  439 + auto mod_date = trailer["/Info"]["/ModDate"];
462 440 if (mod_date.null()) {
463   - trailer.removeKey("/Info");
  441 + trailer.erase("/Info");
464 442 } else {
465   - auto info = trailer.replaceKeyAndGetNew(
466   - "/Info", pdf.makeIndirectObject(QPDFObjectHandle::newDictionary()));
467   - info.replaceKey("/ModDate", mod_date);
  443 + trailer.replaceKey(
  444 + "/Info", pdf.makeIndirectObject(Dictionary({{"/ModDate", mod_date}})));
468 445 }
469   - pdf.getRoot().removeKey("/Metadata");
  446 + root.erase("/Metadata");
470 447 }
471 448 if (m->remove_metadata) {
472   - pdf.getRoot().removeKey("/Metadata");
  449 + root.erase("/Metadata");
473 450 }
474 451 if (m->remove_structure) {
475   - pdf.getRoot().removeKey("/StructTreeRoot");
476   - pdf.getRoot().removeKey("/MarkInfo");
  452 + root.erase("/StructTreeRoot");
  453 + root.erase("/MarkInfo");
477 454 }
478 455  
479   - for (auto& foreign: page_heap) {
480   - if (foreign->anyWarnings()) {
481   - m->warnings = true;
482   - }
483   - }
484 456 return pdf_sp;
485 457 }
486 458  
... ... @@ -595,10 +567,10 @@ QPDFJob::checkConfiguration()
595 567 // standard output.
596 568 m->outfilename = "-";
597 569 }
598   - if (m->infilename.empty() && !m->empty_input) {
  570 + if (m->infile_name().empty() && !m->empty_input) {
599 571 usage("an input file name is required");
600 572 }
601   - if (m->replace_input && m->infilename.empty()) {
  573 + if (m->replace_input && m->infile_name().empty()) {
602 574 usage("--replace-input may not be used with --empty");
603 575 }
604 576 if (m->require_outfile && m->outfilename.empty() && !m->replace_input) {
... ... @@ -637,8 +609,7 @@ QPDFJob::checkConfiguration()
637 609 if (save_to_stdout) {
638 610 m->log->saveToStandardOutput(true);
639 611 }
640   - if (!m->split_pages && QUtil::same_file(m->infilename.data(), m->outfilename.data())) {
641   - QTC::TC("qpdf", "QPDFJob same file error");
  612 + if (!m->split_pages && QUtil::same_file(m->infile_nm(), m->outfilename.data())) {
642 613 usage(
643 614 "input file and output file are the same; use --replace-input to intentionally "
644 615 "overwrite the input file");
... ... @@ -762,7 +733,7 @@ QPDFJob::doCheck(QPDF&amp; pdf)
762 733 // may continue to perform additional checks after finding errors.
763 734 bool okay = true;
764 735 auto& cout = *m->log->getInfo();
765   - cout << "checking " << m->infilename << "\n";
  736 + cout << "checking " << m->infile_name() << "\n";
766 737 QPDF::JobSetter::setCheckMode(pdf, true);
767 738 try {
768 739 int extension_level = pdf.getExtensionLevel();
... ... @@ -933,7 +904,7 @@ QPDFJob::doListAttachments(QPDF&amp; pdf)
933 904 });
934 905 }
935 906 } else {
936   - *m->log->getInfo() << m->infilename << " has no embedded files\n";
  907 + *m->log->getInfo() << m->infile_name() << " has no embedded files\n";
937 908 }
938 909 }
939 910  
... ... @@ -1699,7 +1670,6 @@ QPDFJob::doInspection(QPDF&amp; pdf)
1699 1670 doCheck(pdf);
1700 1671 }
1701 1672 if (m->show_npages) {
1702   - QTC::TC("qpdf", "QPDFJob npages");
1703 1673 cout << pdf.getRoot().getKey("/Pages").getKey("/Count").getIntValue() << "\n";
1704 1674 }
1705 1675 if (m->show_encryption) {
... ... @@ -1707,9 +1677,9 @@ QPDFJob::doInspection(QPDF&amp; pdf)
1707 1677 }
1708 1678 if (m->check_linearization) {
1709 1679 if (!pdf.isLinearized()) {
1710   - cout << m->infilename << " is not linearized\n";
  1680 + cout << m->infile_name() << " is not linearized\n";
1711 1681 } else if (pdf.checkLinearization()) {
1712   - cout << m->infilename << ": no linearization errors\n";
  1682 + cout << m->infile_name() << ": no linearization errors\n";
1713 1683 } else {
1714 1684 m->warnings = true;
1715 1685 }
... ... @@ -1718,7 +1688,7 @@ QPDFJob::doInspection(QPDF&amp; pdf)
1718 1688 if (pdf.isLinearized()) {
1719 1689 pdf.showLinearizationData();
1720 1690 } else {
1721   - cout << m->infilename << " is not linearized\n";
  1691 + cout << m->infile_name() << " is not linearized\n";
1722 1692 }
1723 1693 }
1724 1694 if (m->show_xref) {
... ... @@ -1755,7 +1725,7 @@ QPDFJob::doProcessOnce(
1755 1725 if (empty) {
1756 1726 pdf->emptyPDF();
1757 1727 } else if (main_input && m->json_input) {
1758   - pdf->createFromJSON(m->infilename);
  1728 + pdf->createFromJSON(m->infile_name());
1759 1729 } else {
1760 1730 fn(pdf.get(), password);
1761 1731 }
... ... @@ -1867,25 +1837,22 @@ QPDFJob::processInputSource(
1867 1837 void
1868 1838 QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo)
1869 1839 {
1870   - auto& main_pdh = pdf.pages();
1871   - int main_npages = QIntC::to_int(main_pdh.getAllPages().size());
1872 1840 processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false);
1873   - QPDFPageDocumentHelper uo_pdh(*(uo->pdf));
1874   - int uo_npages = QIntC::to_int(uo_pdh.getAllPages().size());
1875 1841 try {
1876   - uo->to_pagenos = QUtil::parse_numrange(uo->to_nr.c_str(), main_npages);
  1842 + uo->to_pagenos =
  1843 + QUtil::parse_numrange(uo->to_nr.data(), static_cast<int>(pdf.getAllPages().size()));
1877 1844 } catch (std::runtime_error& e) {
1878 1845 throw std::runtime_error(
1879 1846 "parsing numeric range for " + uo->which + " \"to\" pages: " + e.what());
1880 1847 }
1881 1848 try {
1882 1849 if (uo->from_nr.empty()) {
1883   - QTC::TC("qpdf", "QPDFJob from_nr from repeat_nr");
1884 1850 uo->from_nr = uo->repeat_nr;
1885 1851 }
1886   - uo->from_pagenos = QUtil::parse_numrange(uo->from_nr.c_str(), uo_npages);
  1852 + int uo_npages = static_cast<int>(uo->pdf->getAllPages().size());
  1853 + uo->from_pagenos = QUtil::parse_numrange(uo->from_nr.data(), uo_npages);
1887 1854 if (!uo->repeat_nr.empty()) {
1888   - uo->repeat_pagenos = QUtil::parse_numrange(uo->repeat_nr.c_str(), uo_npages);
  1855 + uo->repeat_pagenos = QUtil::parse_numrange(uo->repeat_nr.data(), uo_npages);
1889 1856 }
1890 1857 } catch (std::runtime_error& e) {
1891 1858 throw std::runtime_error(
... ... @@ -1897,29 +1864,28 @@ std::string
1897 1864 QPDFJob::doUnderOverlayForPage(
1898 1865 QPDF& pdf,
1899 1866 UnderOverlay& uo,
1900   - std::map<int, std::map<size_t, std::vector<int>>>& pagenos,
1901   - size_t page_idx,
  1867 + std::vector<std::map<size_t, std::vector<int>>>& pagenos,
  1868 + PageNo const& pageno,
1902 1869 size_t uo_idx,
1903 1870 std::map<int, std::map<size_t, QPDFObjectHandle>>& fo,
1904   - std::vector<QPDFPageObjectHelper>& pages,
1905 1871 QPDFPageObjectHelper& dest_page)
1906 1872 {
1907   - int pageno = 1 + QIntC::to_int(page_idx);
1908   - if (!(pagenos.contains(pageno) && pagenos[pageno].contains(uo_idx))) {
  1873 + if (!(uo.pdf && pagenos[pageno.idx].contains(uo_idx))) {
1909 1874 return "";
1910 1875 }
1911 1876 auto& dest_afdh = dest_page.qpdf()->acroform();
1912 1877  
  1878 + auto const& pages = uo.pdf->getAllPages();
1913 1879 std::string content;
1914 1880 int min_suffix = 1;
1915 1881 QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true);
1916   - for (int from_pageno: pagenos[pageno][uo_idx]) {
  1882 + for (PageNo from_no: pagenos[pageno.idx][uo_idx]) {
1917 1883 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
1918   - v << " " << uo.filename << " " << uo.which << " " << from_pageno << "\n";
  1884 + v << " " << uo.filename << " " << uo.which << " " << from_no.no << "\n";
1919 1885 });
1920   - auto from_page = pages.at(QIntC::to_size(from_pageno - 1));
1921   - if (!fo[from_pageno].contains(uo_idx)) {
1922   - fo[from_pageno][uo_idx] = pdf.copyForeignObject(from_page.getFormXObjectForPage());
  1886 + QPDFPageObjectHelper from_page = pages.at(from_no.idx);
  1887 + if (!fo[from_no.no].contains(uo_idx)) {
  1888 + fo[from_no.no][uo_idx] = pdf.copyForeignObject(from_page.getFormXObjectForPage());
1923 1889 }
1924 1890  
1925 1891 // If the same page is overlaid or underlaid multiple times, we'll generate multiple names
... ... @@ -1927,13 +1893,13 @@ QPDFJob::doUnderOverlayForPage(
1927 1893 std::string name = resources.getUniqueResourceName("/Fx", min_suffix);
1928 1894 QPDFMatrix cm;
1929 1895 std::string new_content = dest_page.placeFormXObject(
1930   - fo[from_pageno][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm);
  1896 + fo[from_no.no][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm);
1931 1897 dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->acroform());
1932 1898 if (!new_content.empty()) {
1933 1899 resources.mergeResources("<< /XObject << >> >>"_qpdf);
1934 1900 auto xobject = resources.getKey("/XObject");
1935 1901 if (xobject.isDictionary()) {
1936   - xobject.replaceKey(name, fo[from_pageno][uo_idx]);
  1902 + xobject.replaceKey(name, fo[from_no.no][uo_idx]);
1937 1903 }
1938 1904 ++min_suffix;
1939 1905 content += new_content;
... ... @@ -1945,14 +1911,15 @@ QPDFJob::doUnderOverlayForPage(
1945 1911 void
1946 1912 QPDFJob::getUOPagenos(
1947 1913 std::vector<QPDFJob::UnderOverlay>& uos,
1948   - std::map<int, std::map<size_t, std::vector<int>>>& pagenos)
  1914 + std::vector<std::map<size_t, std::vector<int>>>& pagenos)
1949 1915 {
1950 1916 size_t uo_idx = 0;
1951 1917 for (auto const& uo: uos) {
1952 1918 size_t page_idx = 0;
1953 1919 size_t from_size = uo.from_pagenos.size();
1954 1920 size_t repeat_size = uo.repeat_pagenos.size();
1955   - for (int to_pageno: uo.to_pagenos) {
  1921 + for (int to_pageno_i: uo.to_pagenos) {
  1922 + size_t to_pageno = static_cast<size_t>(to_pageno_i - 1);
1956 1923 if (page_idx < from_size) {
1957 1924 pagenos[to_pageno][uo_idx].push_back(uo.from_pagenos.at(page_idx));
1958 1925 } else if (repeat_size) {
... ... @@ -1978,67 +1945,47 @@ QPDFJob::handleUnderOverlay(QPDF&amp; pdf)
1978 1945 validateUnderOverlay(pdf, &uo);
1979 1946 }
1980 1947  
1981   - // First map key is 1-based page number. Second is index into the overlay/underlay vector. Watch
1982   - // out to not reverse the keys or be off by one.
1983   - std::map<int, std::map<size_t, std::vector<int>>> underlay_pagenos;
1984   - std::map<int, std::map<size_t, std::vector<int>>> overlay_pagenos;
  1948 + auto const& dest_pages = pdf.getAllPages();
  1949 +
  1950 + // First vector key is 0-based page number. Second is index into the overlay/underlay vector.
  1951 + // Watch out to not reverse the keys or be off by one.
  1952 + std::vector<std::map<size_t, std::vector<int>>> underlay_pagenos(dest_pages.size());
  1953 + std::vector<std::map<size_t, std::vector<int>>> overlay_pagenos(dest_pages.size());
1985 1954 getUOPagenos(m->underlay, underlay_pagenos);
1986 1955 getUOPagenos(m->overlay, overlay_pagenos);
1987 1956 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
1988 1957 v << prefix << ": processing underlay/overlay\n";
1989 1958 });
1990 1959  
1991   - auto get_pages = [](std::vector<UnderOverlay>& v,
1992   - std::vector<std::vector<QPDFPageObjectHelper>>& v_out) {
1993   - for (auto const& uo: v) {
1994   - if (uo.pdf) {
1995   - v_out.push_back(QPDFPageDocumentHelper(*(uo.pdf)).getAllPages());
1996   - }
1997   - }
1998   - };
1999   - std::vector<std::vector<QPDFPageObjectHelper>> upages;
2000   - get_pages(m->underlay, upages);
2001   - std::vector<std::vector<QPDFPageObjectHelper>> opages;
2002   - get_pages(m->overlay, opages);
2003   -
2004 1960 std::map<int, std::map<size_t, QPDFObjectHandle>> underlay_fo;
2005 1961 std::map<int, std::map<size_t, QPDFObjectHandle>> overlay_fo;
2006   - QPDFPageDocumentHelper main_pdh(pdf);
2007   - auto main_pages = main_pdh.getAllPages();
2008   - size_t main_npages = main_pages.size();
2009   - for (size_t page_idx = 0; page_idx < main_npages; ++page_idx) {
2010   - auto pageno = QIntC::to_int(page_idx) + 1;
2011   - doIfVerbose(
2012   - [&](Pipeline& v, std::string const& prefix) { v << " page " << pageno << "\n"; });
2013   - if (underlay_pagenos[pageno].empty() && overlay_pagenos[pageno].empty()) {
  1962 + PageNo dest_page_no;
  1963 + for (QPDFPageObjectHelper dest_page: dest_pages) {
  1964 + doIfVerbose([&](Pipeline& v, std::string const& prefix) {
  1965 + v << " page " << dest_page_no.no << "\n";
  1966 + });
  1967 + if (underlay_pagenos[dest_page_no.idx].empty() &&
  1968 + overlay_pagenos[dest_page_no.idx].empty()) {
  1969 + ++dest_page_no;
2014 1970 continue;
2015 1971 }
2016 1972 // This code converts the original page, any underlays, and any overlays to form XObjects.
2017 1973 // Then it concatenates display of all underlays, the original page, and all overlays. Prior
2018 1974 // to 11.3.0, the original page contents were wrapped in q/Q, but this didn't work if the
2019 1975 // original page had unbalanced q/Q operators. See GitHub issue #904.
2020   - auto& dest_page = main_pages.at(page_idx);
2021   - auto dest_page_oh = dest_page.getObjectHandle();
2022 1976 auto this_page_fo = dest_page.getFormXObjectForPage();
2023 1977 // The resulting form xobject lazily reads the content from the original page, which we are
2024 1978 // going to replace. Therefore, we have to explicitly copy it.
2025 1979 auto content_data = this_page_fo.getRawStreamData();
2026 1980 this_page_fo.replaceStreamData(content_data, QPDFObjectHandle(), QPDFObjectHandle());
2027   - auto resources =
2028   - dest_page_oh.replaceKeyAndGetNew("/Resources", "<< /XObject << >> >>"_qpdf);
2029   - resources.getKey("/XObject").replaceKeyAndGetNew("/Fx0", this_page_fo);
  1981 + auto resources = dest_page.getObjectHandle().replaceKeyAndGetNew(
  1982 + "/Resources", Dictionary({{"/XObject", Dictionary({{"/Fx0", this_page_fo}})}}));
  1983 +
2030 1984 size_t uo_idx{0};
2031 1985 std::string content;
2032 1986 for (auto& underlay: m->underlay) {
2033 1987 content += doUnderOverlayForPage(
2034   - pdf,
2035   - underlay,
2036   - underlay_pagenos,
2037   - page_idx,
2038   - uo_idx,
2039   - underlay_fo,
2040   - upages[uo_idx],
2041   - dest_page);
  1988 + pdf, underlay, underlay_pagenos, dest_page_no, uo_idx, underlay_fo, dest_page);
2042 1989 ++uo_idx;
2043 1990 }
2044 1991 content += dest_page.placeFormXObject(
... ... @@ -2051,17 +1998,11 @@ QPDFJob::handleUnderOverlay(QPDF&amp; pdf)
2051 1998 uo_idx = 0;
2052 1999 for (auto& overlay: m->overlay) {
2053 2000 content += doUnderOverlayForPage(
2054   - pdf,
2055   - overlay,
2056   - overlay_pagenos,
2057   - page_idx,
2058   - uo_idx,
2059   - overlay_fo,
2060   - opages[uo_idx],
2061   - dest_page);
  2001 + pdf, overlay, overlay_pagenos, dest_page_no, uo_idx, overlay_fo, dest_page);
2062 2002 ++uo_idx;
2063 2003 }
2064   - dest_page_oh.replaceKey("/Contents", pdf.newStream(content));
  2004 + dest_page.getObjectHandle().replaceKey("/Contents", pdf.newStream(content));
  2005 + ++dest_page_no;
2065 2006 }
2066 2007 }
2067 2008  
... ... @@ -2389,108 +2330,200 @@ added_page(QPDF&amp; pdf, QPDFPageObjectHelper page)
2389 2330 return added_page(pdf, page.getObjectHandle());
2390 2331 }
2391 2332  
  2333 +// Initialize all members that depend on the QPDF object. If both qpdf and qpdf_p are null do
  2334 +// nothing.
2392 2335 void
2393   -QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_heap)
  2336 +QPDFJob::Input::initialize(Inputs& in, QPDF* a_qpdf)
2394 2337 {
2395   - // Parse all page specifications and translate them into lists of actual pages.
  2338 + qpdf = a_qpdf ? a_qpdf : qpdf_p.get();
  2339 + if (qpdf) {
  2340 + orig_pages = qpdf->getAllPages();
  2341 + n_pages = static_cast<int>(orig_pages.size());
  2342 + copied_pages = std::vector<bool>(orig_pages.size(), false);
2396 2343  
2397   - // Handle "." as a shortcut for the input file
2398   - for (auto& page_spec: m->page_specs) {
2399   - if (page_spec.filename == ".") {
2400   - page_spec.filename = m->infilename;
  2344 + if (in.job.m->remove_unreferenced_page_resources != QPDFJob::re_no) {
  2345 + remove_unreferenced = in.job.shouldRemoveUnreferencedResources(*qpdf);
  2346 + }
  2347 + if (qpdf->page_labels().hasPageLabels()) {
  2348 + in.any_page_labels = true;
2401 2349 }
2402   - if (page_spec.range.empty()) {
2403   - page_spec.range = "1-z";
  2350 + }
  2351 +}
  2352 +
  2353 +void
  2354 +QPDFJob::Inputs::infile_name(std::string const& name)
  2355 +{
  2356 + if (!infile_name_.empty()) {
  2357 + usage("input file has already been given");
  2358 + }
  2359 + infile_name_ = name;
  2360 +
  2361 + auto& in_entry = *files.insert({name, Input()}).first;
  2362 + auto it = files.find("");
  2363 + if (it != files.end()) {
  2364 + // We allready have selection entries for the main input file. We need to fix them to point
  2365 + // to the correct files entry.
  2366 + for (auto& selection: selections) {
  2367 + if (selection.in_entry == &*it) {
  2368 + selection.in_entry = &in_entry;
  2369 + }
2404 2370 }
  2371 + files.erase(it);
  2372 + }
  2373 +}
  2374 +
  2375 +void
  2376 +QPDFJob::Inputs::process(std::string const& filename, QPDFJob::Input& input)
  2377 +{
  2378 + // Open the PDF file and store the QPDF object. Do not canonicalize the file name. Using two
  2379 + // different paths to refer to the same file is a documented workaround for duplicating a page.
  2380 + // If you are using this an example of how to do this with the API, you can just create two
  2381 + // different QPDF objects to the same underlying file with the same path to achieve the
  2382 + // same effect.
  2383 + auto password = input.password;
  2384 + if (!encryption_file.empty() && password.empty() && filename == encryption_file) {
  2385 + password = encryption_file_password;
  2386 + }
  2387 + job.doIfVerbose([&](Pipeline& v, std::string const& prefix) {
  2388 + v << prefix << ": processing " << filename << "\n";
  2389 + });
  2390 + if (!keep_files_open) {
  2391 + auto cis = std::make_shared<ClosedFileInputSource>(filename.data());
  2392 + input.cfis = cis.get();
  2393 + input.cfis->stayOpen(true);
  2394 + job.processInputSource(input.qpdf_p, cis, password.data(), true);
  2395 + } else {
  2396 + job.processInputSource(
  2397 + input.qpdf_p,
  2398 + std::make_shared<FileInputSource>(filename.data()),
  2399 + password.data(),
  2400 + true);
2405 2401 }
  2402 + input.initialize(*this);
2406 2403  
2407   - if (!m->keep_files_open_set) {
  2404 + if (input.cfis) {
  2405 + input.cfis->stayOpen(false);
  2406 + }
  2407 +}
  2408 +
  2409 +void
  2410 +QPDFJob::Inputs::process_all()
  2411 +{
  2412 + if (!infile_name().empty()) {
  2413 + files.erase("");
  2414 + }
  2415 + if (!keep_files_open_set) {
2408 2416 // Count the number of distinct files to determine whether we should keep files open or not.
2409 2417 // Rather than trying to code some portable heuristic based on OS limits, just hard-code
2410 2418 // this at a given number and allow users to override.
2411   - std::set<std::string> filenames;
2412   - for (auto& page_spec: m->page_specs) {
2413   - filenames.insert(page_spec.filename);
2414   - }
2415   - m->keep_files_open = (filenames.size() <= m->keep_files_open_threshold);
2416   - QTC::TC("qpdf", "QPDFJob automatically set keep files open", m->keep_files_open ? 0 : 1);
2417   - doIfVerbose([&](Pipeline& v, std::string const& prefix) {
2418   - v << prefix << ": selecting --keep-open-files=" << (m->keep_files_open ? "y" : "n")
  2419 + keep_files_open = files.size() <= keep_files_open_threshold;
  2420 + QTC::TC("qpdf", "QPDFJob automatically set keep files open", keep_files_open ? 0 : 1);
  2421 + job.doIfVerbose([&](Pipeline& v, std::string const& prefix) {
  2422 + v << prefix << ": selecting --keep-open-files=" << (keep_files_open ? "y" : "n")
2419 2423 << "\n";
2420 2424 });
2421 2425 }
2422 2426  
2423   - // Create a QPDF object for each file that we may take pages from.
2424   - std::map<std::string, QPDF*> page_spec_qpdfs;
2425   - std::map<std::string, ClosedFileInputSource*> page_spec_cfis;
2426   - page_spec_qpdfs[m->infilename] = &pdf;
2427   - std::vector<QPDFPageData> parsed_specs;
2428   - std::map<unsigned long long, std::set<QPDFObjGen>> copied_pages;
2429   - for (auto& page_spec: m->page_specs) {
2430   - if (!page_spec_qpdfs.contains(page_spec.filename)) {
2431   - // Open the PDF file and store the QPDF object. Throw a std::shared_ptr to the qpdf into
2432   - // a heap so that it survives through copying to the output but gets cleaned up
2433   - // automatically at the end. Do not canonicalize the file name. Using two different
2434   - // paths to refer to the same file is a documented workaround for duplicating a page. If
2435   - // you are using this an example of how to do this with the API, you can just create two
2436   - // different QPDF objects to the same underlying file with the same path to achieve the
2437   - // same effect.
2438   - auto password = page_spec.password;
2439   - if (!m->encryption_file.empty() && password.empty() &&
2440   - page_spec.filename == m->encryption_file) {
2441   - QTC::TC("qpdf", "QPDFJob pages encryption password");
2442   - password = m->encryption_file_password;
  2427 + for (auto& [filename, input]: files) {
  2428 + if (!input.qpdf) {
  2429 + process(filename, input);
  2430 + }
  2431 +
  2432 + for (auto& selection: selections) {
  2433 + if (&selection.input() != &input) {
  2434 + continue;
2443 2435 }
2444   - doIfVerbose([&](Pipeline& v, std::string const& prefix) {
2445   - v << prefix << ": processing " << page_spec.filename << "\n";
2446   - });
2447   - std::shared_ptr<InputSource> is;
2448   - ClosedFileInputSource* cis = nullptr;
2449   - if (!m->keep_files_open) {
2450   - QTC::TC("qpdf", "QPDFJob keep files open n");
2451   - cis = new ClosedFileInputSource(page_spec.filename.c_str());
2452   - is = std::shared_ptr<InputSource>(cis);
2453   - cis->stayOpen(true);
2454   - } else {
2455   - QTC::TC("qpdf", "QPDFJob keep files open y");
2456   - FileInputSource* fis = new FileInputSource(page_spec.filename.c_str());
2457   - is = std::shared_ptr<InputSource>(fis);
  2436 + // Read original pages from the PDF, and parse the page range associated with this
  2437 + // occurrence of the file.
  2438 + if (selection.range.empty()) {
  2439 + selection.selected_pages.reserve(static_cast<size_t>(input.n_pages));
  2440 + for (int i = 1; i <= input.n_pages; ++i) {
  2441 + selection.selected_pages.push_back(i);
  2442 + }
  2443 + continue;
2458 2444 }
2459   - std::unique_ptr<QPDF> qpdf_sp;
2460   - processInputSource(qpdf_sp, is, password.data(), true);
2461   - page_spec_qpdfs[page_spec.filename] = qpdf_sp.get();
2462   - page_heap.push_back(std::move(qpdf_sp));
2463   - if (cis) {
2464   - cis->stayOpen(false);
2465   - page_spec_cfis[page_spec.filename] = cis;
  2445 + try {
  2446 + selection.selected_pages =
  2447 + QUtil::parse_numrange(selection.range.data(), selection.input().n_pages);
  2448 + } catch (std::runtime_error& e) {
  2449 + throw std::runtime_error(
  2450 + "parsing numeric range for " + selection.filename() + ": " + e.what());
2466 2451 }
2467 2452 }
2468   -
2469   - // Read original pages from the PDF, and parse the page range associated with this
2470   - // occurrence of the file.
2471   - parsed_specs.emplace_back(
2472   - page_spec.filename, page_spec_qpdfs[page_spec.filename], page_spec.range);
2473 2453 }
  2454 +}
2474 2455  
2475   - std::map<unsigned long long, bool> remove_unreferenced;
2476   - if (m->remove_unreferenced_page_resources != QPDFJob::re_no) {
2477   - for (auto const& iter: page_spec_qpdfs) {
2478   - std::string const& filename = iter.first;
2479   - ClosedFileInputSource* cis = nullptr;
2480   - if (page_spec_cfis.contains(filename)) {
2481   - cis = page_spec_cfis[filename];
2482   - cis->stayOpen(true);
2483   - }
2484   - QPDF& other(*(iter.second));
2485   - auto other_uuid = other.getUniqueId();
2486   - if (!remove_unreferenced.contains(other_uuid)) {
2487   - remove_unreferenced[other_uuid] = shouldRemoveUnreferencedResources(other);
2488   - }
2489   - if (cis) {
2490   - cis->stayOpen(false);
2491   - }
  2456 +bool
  2457 +QPDFJob::Inputs::clear()
  2458 +{
  2459 + bool any_warnings = false;
  2460 + for (auto& [filename, file_spec]: files) {
  2461 + if (auto& pdf = file_spec.qpdf_p) {
  2462 + any_warnings |= pdf->anyWarnings();
  2463 + pdf = nullptr;
2492 2464 }
2493 2465 }
  2466 + return any_warnings;
  2467 +}
  2468 +
  2469 +QPDFJob::Selection&
  2470 +QPDFJob::Inputs::new_selection(std::string const& filename)
  2471 +{
  2472 + // Handle "." as a shortcut for the input file. Note that infile_name may not be known yet, in
  2473 + // which case we are wrongly entering an empty name. This will be corrected in the infile_name
  2474 + // setter.
  2475 + return selections.emplace_back(
  2476 + *files.insert({(filename == "." ? infile_name() : filename), Input()}).first);
  2477 +}
  2478 +
  2479 +void
  2480 +QPDFJob::Inputs::new_selection(
  2481 + std::string const& filename, std::string const& password, std::string const& range)
  2482 +{
  2483 + auto& selection = new_selection(filename);
  2484 + selection.password(password);
  2485 + selection.range = range;
  2486 +}
  2487 +
  2488 +QPDFJob::Selection::Selection(std::pair<const std::string, QPDFJob::Input>& entry) :
  2489 + in_entry(&entry)
  2490 +{
  2491 +}
  2492 +
  2493 +QPDFJob::Input&
  2494 +QPDFJob::Selection::input()
  2495 +{
  2496 + return in_entry->second;
  2497 +}
  2498 +
  2499 +std::string const&
  2500 +QPDFJob::Selection::filename()
  2501 +{
  2502 + return in_entry->first;
  2503 +}
  2504 +
  2505 +void
  2506 +QPDFJob::Selection::password(std::string password)
  2507 +{
  2508 + auto& in = input();
  2509 + if (!in.password.empty()) {
  2510 + usage("--password already specified for this file");
  2511 + }
  2512 + in.password = password;
  2513 +}
  2514 +
  2515 +// Handle all page specifications.
  2516 +void
  2517 +QPDFJob::handlePageSpecs(QPDF& pdf)
  2518 +{
  2519 + if (m->inputs.selections.empty()) {
  2520 + return;
  2521 + }
  2522 + auto& main_input = m->inputs.files[m->infile_name()];
  2523 + main_input.initialize(m->inputs, &pdf);
  2524 +
  2525 + // Parse all section and translate them into lists of actual pages.
  2526 + m->inputs.process_all();
2494 2527  
2495 2528 // Clear all pages out of the primary QPDF's pages tree but leave the objects in place in the
2496 2529 // file so they can be re-added without changing their object numbers. This enables other things
... ... @@ -2498,23 +2531,22 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2498 2531 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
2499 2532 v << prefix << ": removing unreferenced pages from primary input\n";
2500 2533 });
2501   - QPDFPageDocumentHelper dh(pdf);
2502   - std::vector<QPDFPageObjectHelper> orig_pages = dh.getAllPages();
2503   - for (auto const& page: orig_pages) {
2504   - dh.removePage(page);
  2534 + for (auto const& page: main_input.orig_pages) {
  2535 + pdf.removePage(page);
2505 2536 }
2506 2537  
2507 2538 auto n_collate = m->collate.size();
2508   - auto n_specs = parsed_specs.size();
  2539 + auto n_specs = m->inputs.selections.size();
2509 2540 if (!(n_collate == 0 || n_collate == 1 || n_collate == n_specs)) {
2510 2541 usage(
2511 2542 "--pages: if --collate has more than one value, it must have one value per page "
2512 2543 "specification");
2513 2544 }
  2545 +
  2546 + std::vector<Selection> new_specs;
2514 2547 if (n_collate > 0 && n_specs > 1) {
2515 2548 // Collate the pages by selecting one page from each spec in order. When a spec runs out of
2516 2549 // pages, stop selecting from it.
2517   - std::vector<QPDFPageData> new_parsed_specs;
2518 2550 // Make sure we have a collate value for each spec. We have already checked that a non-empty
2519 2551 // collate has either one value or one value per spec.
2520 2552 for (auto i = n_collate; i < n_specs; ++i) {
... ... @@ -2525,70 +2557,55 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2525 2557 while (got_pages) {
2526 2558 got_pages = false;
2527 2559 for (size_t i = 0; i < n_specs; ++i) {
2528   - QPDFPageData& page_data = parsed_specs.at(i);
  2560 + auto& page_data = m->inputs.selections.at(i);
2529 2561 for (size_t j = 0; j < m->collate.at(i); ++j) {
2530 2562 if (cur_page.at(i) + j < page_data.selected_pages.size()) {
2531 2563 got_pages = true;
2532   - new_parsed_specs.emplace_back(
  2564 + new_specs.emplace_back(
2533 2565 page_data, page_data.selected_pages.at(cur_page.at(i) + j));
2534 2566 }
2535 2567 }
2536 2568 cur_page.at(i) += m->collate.at(i);
2537 2569 }
2538 2570 }
2539   - parsed_specs = new_parsed_specs;
2540 2571 }
2541 2572  
2542 2573 // Add all the pages from all the files in the order specified. Keep track of any pages from the
2543 2574 // original file that we are selecting.
2544   - std::set<int> selected_from_orig;
2545 2575 std::vector<QPDFObjectHandle> new_labels;
2546   - bool any_page_labels = false;
2547 2576 int out_pageno = 0;
2548 2577 auto& this_afdh = pdf.acroform();
2549 2578 std::set<QPDFObjGen> referenced_fields;
2550   - for (auto& page_data: parsed_specs) {
2551   - ClosedFileInputSource* cis = nullptr;
2552   - if (page_spec_cfis.contains(page_data.filename)) {
2553   - cis = page_spec_cfis[page_data.filename];
2554   - cis->stayOpen(true);
2555   - }
2556   - auto& pldh = page_data.qpdf->page_labels();
2557   - auto& other_afdh = page_data.qpdf->acroform();
2558   - if (pldh.hasPageLabels()) {
2559   - any_page_labels = true;
  2579 + for (auto& selection: new_specs.empty() ? m->inputs.selections : new_specs) {
  2580 + auto& input = selection.input();
  2581 + if (input.cfis) {
  2582 + input.cfis->stayOpen(true);
2560 2583 }
  2584 + auto* pldh = m->inputs.any_page_labels ? &input.qpdf->page_labels() : nullptr;
  2585 + auto& other_afdh = input.qpdf->acroform();
2561 2586 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
2562   - v << prefix << ": adding pages from " << page_data.filename << "\n";
  2587 + v << prefix << ": adding pages from " << selection.filename() << "\n";
2563 2588 });
2564   - for (auto pageno_iter: page_data.selected_pages) {
  2589 + const bool this_file = input.qpdf == &pdf;
  2590 + for (PageNo page: selection.selected_pages) {
  2591 + bool first_copy_from_orig = this_file && !main_input.copied_pages[page.idx];
  2592 +
2565 2593 // Pages are specified from 1 but numbered from 0 in the vector
2566   - int pageno = pageno_iter - 1;
2567   - pldh.getLabelsForPageRange(pageno, pageno, out_pageno++, new_labels);
2568   - QPDFPageObjectHelper to_copy = page_data.orig_pages.at(QIntC::to_size(pageno));
2569   - QPDFObjGen to_copy_og = to_copy.getObjectHandle().getObjGen();
2570   - unsigned long long from_uuid = page_data.qpdf->getUniqueId();
2571   - if (copied_pages[from_uuid].contains(to_copy_og)) {
2572   - QTC::TC(
2573   - "qpdf",
2574   - "QPDFJob copy same page more than once",
2575   - (page_data.qpdf == &pdf) ? 0 : 1);
  2594 + int pageno = page.no - 1;
  2595 + if (pldh) {
  2596 + pldh->getLabelsForPageRange(pageno, pageno, out_pageno++, new_labels);
  2597 + }
  2598 + QPDFPageObjectHelper to_copy = input.orig_pages.at(page.idx);
  2599 + if (input.copied_pages[page.idx]) {
  2600 + QTC::TC("qpdf", "QPDFJob copy same page more than once", this_file ? 0 : 1);
2576 2601 to_copy = to_copy.shallowCopyPage();
2577 2602 } else {
2578   - copied_pages[from_uuid].insert(to_copy_og);
2579   - if (remove_unreferenced[from_uuid]) {
  2603 + input.copied_pages[page.idx] = true;
  2604 + if (input.remove_unreferenced) {
2580 2605 to_copy.removeUnreferencedResources();
2581 2606 }
2582 2607 }
2583   - dh.addPage(to_copy, false);
2584   - bool first_copy_from_orig = false;
2585   - bool this_file = (page_data.qpdf == &pdf);
2586   - if (this_file) {
2587   - // This is a page from the original file. Keep track of the fact that we are using
2588   - // it.
2589   - first_copy_from_orig = (!selected_from_orig.contains(pageno));
2590   - selected_from_orig.insert(pageno);
2591   - }
  2608 + pdf.addPage(to_copy, false);
2592 2609 auto new_page = added_page(pdf, to_copy);
2593 2610 // Try to avoid gratuitously renaming fields. In the case of where we're just extracting
2594 2611 // a bunch of pages from the original file and not copying any page more than once,
... ... @@ -2616,48 +2633,46 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2616 2633 }
2617 2634 }
2618 2635 }
2619   - if (cis) {
2620   - cis->stayOpen(false);
  2636 + if (input.cfis) {
  2637 + input.cfis->stayOpen(false);
2621 2638 }
2622 2639 }
2623   - if (any_page_labels) {
2624   - QPDFObjectHandle page_labels = QPDFObjectHandle::newDictionary();
2625   - page_labels.replaceKey("/Nums", QPDFObjectHandle::newArray(new_labels));
2626   - pdf.getRoot().replaceKey("/PageLabels", page_labels);
  2640 + if (m->inputs.any_page_labels) {
  2641 + pdf.getRoot().replaceKey("/PageLabels", Dictionary({{"/Nums", Array(new_labels)}}));
2627 2642 }
2628 2643  
2629 2644 // Delete page objects for unused page in primary. This prevents those objects from being
2630 2645 // preserved by being referred to from other places, such as the outlines dictionary. Also make
2631 2646 // sure we keep form fields from pages we preserved.
2632   - for (size_t pageno = 0; pageno < orig_pages.size(); ++pageno) {
2633   - auto page = orig_pages.at(pageno);
2634   - if (selected_from_orig.contains(QIntC::to_int(pageno))) {
  2647 + size_t page_idx = 0;
  2648 + for (auto const& page: main_input.orig_pages) {
  2649 + if (main_input.copied_pages[page_idx]) {
2635 2650 for (auto field: this_afdh.getFormFieldsForPage(page)) {
2636   - QTC::TC("qpdf", "QPDFJob pages keeping field from original");
2637   - referenced_fields.insert(field.getObjectHandle().getObjGen());
  2651 + referenced_fields.insert(field);
2638 2652 }
2639 2653 } else {
2640   - pdf.replaceObject(page.getObjectHandle().getObjGen(), QPDFObjectHandle::newNull());
  2654 + pdf.replaceObject(page, QPDFObjectHandle::newNull());
2641 2655 }
  2656 + ++page_idx;
2642 2657 }
2643 2658 // Remove unreferenced form fields
2644 2659 if (this_afdh.hasAcroForm()) {
2645   - auto acroform = pdf.getRoot().getKey("/AcroForm");
2646   - auto fields = acroform.getKey("/Fields");
2647   - if (fields.isArray()) {
2648   - auto new_fields = QPDFObjectHandle::newArray();
2649   - if (fields.isIndirect()) {
2650   - new_fields = pdf.makeIndirectObject(new_fields);
2651   - }
2652   - for (auto const& field: fields.aitems()) {
  2660 + auto acroform = pdf.getRoot()["/AcroForm"];
  2661 + if (Array fields = acroform["/Fields"]) {
  2662 + std::vector<QPDFObjectHandle> new_fields;
  2663 + new_fields.reserve(referenced_fields.size());
  2664 + for (auto const& field: fields) {
2653 2665 if (referenced_fields.contains(field.getObjGen())) {
2654   - new_fields.appendItem(field);
  2666 + new_fields.emplace_back(field);
2655 2667 }
2656 2668 }
2657 2669 if (new_fields.empty()) {
2658   - pdf.getRoot().removeKey("/AcroForm");
  2670 + pdf.getRoot().erase("/AcroForm");
2659 2671 } else {
2660   - acroform.replaceKey("/Fields", new_fields);
  2672 + acroform.replaceKey(
  2673 + "/Fields",
  2674 + fields.indirect() ? pdf.makeIndirectObject(Array(new_fields))
  2675 + : QPDFObjectHandle(Array(new_fields)));
2661 2676 }
2662 2677 }
2663 2678 }
... ... @@ -2924,8 +2939,8 @@ QPDFJob::setWriterOptions(QPDFWriter&amp; w)
2924 2939 std::unique_ptr<QPDF> encryption_pdf;
2925 2940 processFile(
2926 2941 encryption_pdf,
2927   - m->encryption_file.data(),
2928   - m->encryption_file_password.data(),
  2942 + m->inputs.encryption_file.data(),
  2943 + m->inputs.encryption_file_password.data(),
2929 2944 false,
2930 2945 false);
2931 2946 w.copyEncryptionParameters(*encryption_pdf);
... ... @@ -3045,7 +3060,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3045 3060 page_range += "-" + QUtil::uint_to_string(last, QIntC::to_int(pageno_len));
3046 3061 }
3047 3062 std::string outfile = before + page_range + after;
3048   - if (QUtil::same_file(m->infilename.data(), outfile.data())) {
  3063 + if (QUtil::same_file(m->infile_nm(), outfile.data())) {
3049 3064 throw std::runtime_error("split pages would overwrite input file with " + outfile);
3050 3065 }
3051 3066 QPDFWriter w(outpdf, outfile.c_str());
... ... @@ -3064,7 +3079,7 @@ QPDFJob::writeOutfile(QPDF&amp; pdf)
3064 3079 if (m->replace_input) {
3065 3080 // Append but don't prepend to the path to generate a temporary name. This saves us from
3066 3081 // having to split the path by directory and non-directory.
3067   - temp_out = m->infilename + ".~qpdf-temp#";
  3082 + temp_out = m->infile_name() + ".~qpdf-temp#";
3068 3083 // m->outfilename will be restored to 0 before temp_out goes out of scope.
3069 3084 m->outfilename = temp_out;
3070 3085 } else if (m->outfilename == "-") {
... ... @@ -3098,13 +3113,13 @@ QPDFJob::writeOutfile(QPDF&amp; pdf)
3098 3113 if (m->replace_input) {
3099 3114 // We must close the input before we can rename files
3100 3115 pdf.closeInputSource();
3101   - std::string backup = m->infilename + ".~qpdf-orig";
  3116 + std::string backup = m->infile_name() + ".~qpdf-orig";
3102 3117 bool warnings = pdf.anyWarnings();
3103 3118 if (!warnings) {
3104 3119 backup.append(1, '#');
3105 3120 }
3106   - QUtil::rename_file(m->infilename.data(), backup.data());
3107   - QUtil::rename_file(temp_out.data(), m->infilename.data());
  3121 + QUtil::rename_file(m->infile_nm(), backup.data());
  3122 + QUtil::rename_file(temp_out.data(), m->infile_nm());
3108 3123 if (warnings) {
3109 3124 *m->log->getError() << m->message_prefix
3110 3125 << ": there are warnings; original file kept in " << backup << "\n";
... ...
libqpdf/QPDFJob_config.cc
1   -#include <qpdf/QPDFJob.hh>
  1 +#include <qpdf/QPDFJob_private.hh>
2 2  
3 3 #include <regex>
4 4  
... ... @@ -15,18 +15,14 @@ QPDFJob::Config::checkConfiguration()
15 15 QPDFJob::Config*
16 16 QPDFJob::Config::inputFile(std::string const& filename)
17 17 {
18   - if (o.m->infilename.empty()) {
19   - o.m->infilename = filename;
20   - } else {
21   - usage("input file has already been given");
22   - }
  18 + o.m->inputs.infile_name(filename);
23 19 return this;
24 20 }
25 21  
26 22 QPDFJob::Config*
27 23 QPDFJob::Config::emptyInput()
28 24 {
29   - if (o.m->infilename.empty()) {
  25 + if (o.m->infile_name().empty()) {
30 26 // Various places in QPDFJob.cc used to know that the empty string for infile means empty.
31 27 // This approach meant that passing "" as the argument to inputFile in job JSON, or
32 28 // equivalently using "" as a positional command-line argument would be the same as
... ... @@ -152,7 +148,7 @@ QPDFJob::Config::copyEncryption(std::string const&amp; parameter)
152 148 if (o.m->deterministic_id) {
153 149 usage("the deterministic-id option is incompatible with encrypted output files");
154 150 }
155   - o.m->encryption_file = parameter;
  151 + o.m->inputs.encryption_file = parameter;
156 152 o.m->copy_encryption = true;
157 153 o.m->encrypt = false;
158 154 o.m->decrypt = false;
... ... @@ -181,7 +177,7 @@ QPDFJob::Config::deterministicId()
181 177 QPDFJob::Config*
182 178 QPDFJob::Config::encryptionFilePassword(std::string const& parameter)
183 179 {
184   - o.m->encryption_file_password = parameter;
  180 + o.m->inputs.encryption_file_password = parameter;
185 181 return this;
186 182 }
187 183  
... ... @@ -354,15 +350,15 @@ QPDFJob::Config::testJsonSchema()
354 350 QPDFJob::Config*
355 351 QPDFJob::Config::keepFilesOpen(std::string const& parameter)
356 352 {
357   - o.m->keep_files_open_set = true;
358   - o.m->keep_files_open = (parameter == "y");
  353 + o.m->inputs.keep_files_open_set = true;
  354 + o.m->inputs.keep_files_open = (parameter == "y");
359 355 return this;
360 356 }
361 357  
362 358 QPDFJob::Config*
363 359 QPDFJob::Config::keepFilesOpenThreshold(std::string const& parameter)
364 360 {
365   - o.m->keep_files_open_threshold = QUtil::string_to_uint(parameter.c_str());
  361 + o.m->inputs.keep_files_open_threshold = QUtil::string_to_uint(parameter.c_str());
366 362 return this;
367 363 }
368 364  
... ... @@ -978,7 +974,7 @@ QPDFJob::PagesConfig::PagesConfig(Config* c) :
978 974 std::shared_ptr<QPDFJob::PagesConfig>
979 975 QPDFJob::Config::pages()
980 976 {
981   - if (!o.m->page_specs.empty()) {
  977 + if (!o.m->inputs.selections.empty()) {
982 978 usage("--pages may only be specified one time");
983 979 }
984 980 return std::shared_ptr<PagesConfig>(new PagesConfig(this));
... ... @@ -987,7 +983,7 @@ QPDFJob::Config::pages()
987 983 QPDFJob::Config*
988 984 QPDFJob::PagesConfig::endPages()
989 985 {
990   - auto n_specs = config->o.m->page_specs.size();
  986 + auto n_specs = config->o.m->inputs.selections.size();
991 987 if (n_specs == 0) {
992 988 usage("--pages: no page specifications given");
993 989 }
... ... @@ -998,27 +994,25 @@ QPDFJob::PagesConfig*
998 994 QPDFJob::PagesConfig::pageSpec(
999 995 std::string const& filename, std::string const& range, char const* password)
1000 996 {
1001   - config->o.m->page_specs.emplace_back(filename, password, range);
  997 + config->o.m->inputs.new_selection(filename, {password ? password : ""}, range);
1002 998 return this;
1003 999 }
1004 1000  
1005 1001 QPDFJob::PagesConfig*
1006 1002 QPDFJob::PagesConfig::file(std::string const& arg)
1007 1003 {
1008   - config->o.m->page_specs.emplace_back(arg, "", "");
  1004 + (void)config->o.m->inputs.new_selection(arg);
1009 1005 return this;
1010 1006 }
1011 1007  
1012 1008 QPDFJob::PagesConfig*
1013 1009 QPDFJob::PagesConfig::range(std::string const& arg)
1014 1010 {
1015   - if (config->o.m->page_specs.empty()) {
1016   - QTC::TC("qpdf", "QPDFJob misplaced page range");
  1011 + if (config->o.m->inputs.selections.empty()) {
1017 1012 usage("in --range must follow a file name");
1018 1013 }
1019   - auto& last = config->o.m->page_specs.back();
  1014 + auto& last = config->o.m->inputs.selections.back();
1020 1015 if (!last.range.empty()) {
1021   - QTC::TC("qpdf", "QPDFJob duplicated range");
1022 1016 usage("--range already specified for this file");
1023 1017 }
1024 1018 last.range = arg;
... ... @@ -1028,16 +1022,10 @@ QPDFJob::PagesConfig::range(std::string const&amp; arg)
1028 1022 QPDFJob::PagesConfig*
1029 1023 QPDFJob::PagesConfig::password(std::string const& arg)
1030 1024 {
1031   - if (config->o.m->page_specs.empty()) {
1032   - QTC::TC("qpdf", "QPDFJob misplaced pages password");
  1025 + if (config->o.m->inputs.selections.empty()) {
1033 1026 usage("in --pages, --password must follow a file name");
1034 1027 }
1035   - auto& last = config->o.m->page_specs.back();
1036   - if (!last.password.empty()) {
1037   - QTC::TC("qpdf", "QPDFJob duplicated pages password");
1038   - usage("--password already specified for this file");
1039   - }
1040   - last.password = arg;
  1028 + config->o.m->inputs.selections.back().password(arg);
1041 1029 return this;
1042 1030 }
1043 1031  
... ...
libqpdf/QPDFJob_json.cc
1   -#include <qpdf/QPDFJob.hh>
  1 +#include <qpdf/QPDFJob_private.hh>
2 2  
3 3 #include <qpdf/JSONHandler.hh>
4 4 #include <qpdf/QPDFUsage.hh>
... ...
libqpdf/qpdf/QPDFJob_private.hh 0 โ†’ 100644
  1 +#ifndef QPDFJOB_PRIVATE_HH
  2 +#define QPDFJOB_PRIVATE_HH
  3 +
  4 +#include <qpdf/QPDFJob.hh>
  5 +
  6 +#include <qpdf/ClosedFileInputSource.hh>
  7 +#include <qpdf/QPDFLogger.hh>
  8 +
  9 +// A selection of pages from a single input PDF to be included in the output. This corresponds to a
  10 +// single clause in the --pages option.
  11 +struct QPDFJob::Selection
  12 +{
  13 + Selection() = delete;
  14 +
  15 + Selection(std::pair<const std::string, Input>& entry);
  16 +
  17 + Selection(Selection const& other, int page) :
  18 + in_entry(other.in_entry),
  19 + selected_pages({page})
  20 + {
  21 + }
  22 +
  23 + QPDFJob::Input& input();
  24 + std::string const& filename();
  25 + void password(std::string password);
  26 +
  27 + std::pair<const std::string, QPDFJob::Input>* in_entry{nullptr};
  28 + std::string range; // An empty range means all pages.
  29 + std::vector<int> selected_pages;
  30 +};
  31 +
  32 +// A single input PDF.
  33 +//
  34 +// N.B. A single input PDF may be represented by multiple Input instances using variations of the
  35 +// filename. This is a documented work-around.
  36 +struct QPDFJob::Input
  37 +{
  38 + void initialize(Inputs& in, QPDF* qpdf = nullptr);
  39 +
  40 + std::string password;
  41 + std::unique_ptr<QPDF> qpdf_p;
  42 + QPDF* qpdf;
  43 + ClosedFileInputSource* cfis{};
  44 + std::vector<QPDFObjectHandle> orig_pages;
  45 + int n_pages;
  46 + std::vector<bool> copied_pages;
  47 + bool remove_unreferenced{false};
  48 +};
  49 +
  50 +// All PDF input files for a job.
  51 +struct QPDFJob::Inputs
  52 +{
  53 + friend struct Input;
  54 + // These default values are duplicated in help and docs.
  55 + static int constexpr DEFAULT_KEEP_FILES_OPEN_THRESHOLD = 200;
  56 +
  57 + Inputs(QPDFJob& job) :
  58 + job(job)
  59 + {
  60 + }
  61 + void process(std::string const& filename, QPDFJob::Input& file_spec);
  62 + void process_all();
  63 +
  64 + // Destroy all owned QPDF objects. Return false if any of the QPDF objects recorded warnings.
  65 + bool clear();
  66 +
  67 + Selection& new_selection(std::string const& filename);
  68 +
  69 + void new_selection(
  70 + std::string const& filename, std::string const& password, std::string const& range);
  71 +
  72 + std::string const&
  73 + infile_name() const
  74 + {
  75 + return infile_name_;
  76 + }
  77 +
  78 + void infile_name(std::string const& name);
  79 +
  80 + std::string infile_name_;
  81 + std::string encryption_file;
  82 + std::string encryption_file_password;
  83 + bool keep_files_open{true};
  84 + bool keep_files_open_set{false};
  85 + size_t keep_files_open_threshold{DEFAULT_KEEP_FILES_OPEN_THRESHOLD};
  86 +
  87 + std::map<std::string, Input> files;
  88 + std::vector<Selection> selections;
  89 +
  90 + bool any_page_labels{false};
  91 +
  92 + private:
  93 + QPDFJob& job;
  94 +};
  95 +
  96 +struct QPDFJob::RotationSpec
  97 +{
  98 + RotationSpec(int angle = 0, bool relative = false) :
  99 + angle(angle),
  100 + relative(relative)
  101 + {
  102 + }
  103 +
  104 + int angle;
  105 + bool relative;
  106 +};
  107 +
  108 +struct QPDFJob::UnderOverlay
  109 +{
  110 + UnderOverlay(char const* which) :
  111 + which(which),
  112 + to_nr("1-z"),
  113 + from_nr("1-z"),
  114 + repeat_nr("")
  115 + {
  116 + }
  117 +
  118 + std::string which;
  119 + std::string filename;
  120 + std::string password;
  121 + std::string to_nr;
  122 + std::string from_nr;
  123 + std::string repeat_nr;
  124 + std::unique_ptr<QPDF> pdf;
  125 + std::vector<int> to_pagenos;
  126 + std::vector<int> from_pagenos;
  127 + std::vector<int> repeat_pagenos;
  128 +};
  129 +
  130 +struct QPDFJob::PageLabelSpec
  131 +{
  132 + PageLabelSpec(
  133 + int first_page, qpdf_page_label_e label_type, int start_num, std::string_view prefix) :
  134 + first_page(first_page),
  135 + label_type(label_type),
  136 + start_num(start_num),
  137 + prefix(prefix)
  138 + {
  139 + }
  140 + int first_page;
  141 + qpdf_page_label_e label_type;
  142 + int start_num{1};
  143 + std::string prefix;
  144 +};
  145 +
  146 +class QPDFJob::Members
  147 +{
  148 + friend class QPDFJob;
  149 +
  150 + public:
  151 + Members(QPDFJob& job) :
  152 + log(QPDFLogger::defaultLogger()),
  153 + inputs(job)
  154 + {
  155 + }
  156 + Members(Members const&) = delete;
  157 + ~Members() = default;
  158 +
  159 + inline const char* infile_nm() const;
  160 +
  161 + inline std::string const& infile_name() const;
  162 +
  163 + private:
  164 + // These default values are duplicated in help and docs.
  165 + static int constexpr DEFAULT_OI_MIN_WIDTH = 128;
  166 + static int constexpr DEFAULT_OI_MIN_HEIGHT = 128;
  167 + static int constexpr DEFAULT_OI_MIN_AREA = 16384;
  168 + static int constexpr DEFAULT_II_MIN_BYTES = 1024;
  169 +
  170 + std::shared_ptr<QPDFLogger> log;
  171 + std::string message_prefix{"qpdf"};
  172 + bool warnings{false};
  173 + unsigned long encryption_status{0};
  174 + bool verbose{false};
  175 + std::string password;
  176 + bool linearize{false};
  177 + bool decrypt{false};
  178 + bool remove_restrictions{false};
  179 + int split_pages{0};
  180 + bool progress{false};
  181 + std::function<void(int)> progress_handler{nullptr};
  182 + bool suppress_warnings{false};
  183 + bool warnings_exit_zero{false};
  184 + bool copy_encryption{false};
  185 + bool encrypt{false};
  186 + bool password_is_hex_key{false};
  187 + bool suppress_password_recovery{false};
  188 + password_mode_e password_mode{pm_auto};
  189 + bool allow_insecure{false};
  190 + bool allow_weak_crypto{false};
  191 + std::string user_password;
  192 + std::string owner_password;
  193 + int keylen{0};
  194 + bool r2_print{true};
  195 + bool r2_modify{true};
  196 + bool r2_extract{true};
  197 + bool r2_annotate{true};
  198 + bool r3_accessibility{true};
  199 + bool r3_extract{true};
  200 + bool r3_assemble{true};
  201 + bool r3_annotate_and_form{true};
  202 + bool r3_form_filling{true};
  203 + bool r3_modify_other{true};
  204 + qpdf_r3_print_e r3_print{qpdf_r3p_full};
  205 + bool force_V4{false};
  206 + bool force_R5{false};
  207 + bool cleartext_metadata{false};
  208 + bool use_aes{false};
  209 + bool stream_data_set{false};
  210 + qpdf_stream_data_e stream_data_mode{qpdf_s_compress};
  211 + bool compress_streams{true};
  212 + bool compress_streams_set{false};
  213 + bool recompress_flate{false};
  214 + bool recompress_flate_set{false};
  215 + int compression_level{-1};
  216 + int jpeg_quality{-1};
  217 + qpdf_stream_decode_level_e decode_level{qpdf_dl_generalized};
  218 + bool decode_level_set{false};
  219 + bool normalize_set{false};
  220 + bool normalize{false};
  221 + bool suppress_recovery{false};
  222 + bool object_stream_set{false};
  223 + qpdf_object_stream_e object_stream_mode{qpdf_o_preserve};
  224 + bool ignore_xref_streams{false};
  225 + bool qdf_mode{false};
  226 + bool preserve_unreferenced_objects{false};
  227 + remove_unref_e remove_unreferenced_page_resources{re_auto};
  228 + bool newline_before_endstream{false};
  229 + std::string linearize_pass1;
  230 + bool coalesce_contents{false};
  231 + bool flatten_annotations{false};
  232 + int flatten_annotations_required{0};
  233 + int flatten_annotations_forbidden{an_invisible | an_hidden};
  234 + bool generate_appearances{false};
  235 + PDFVersion max_input_version;
  236 + std::string min_version;
  237 + std::string force_version;
  238 + bool show_npages{false};
  239 + bool deterministic_id{false};
  240 + bool static_id{false};
  241 + bool static_aes_iv{false};
  242 + bool suppress_original_object_id{false};
  243 + bool show_encryption{false};
  244 + bool show_encryption_key{false};
  245 + bool check_linearization{false};
  246 + bool show_linearization{false};
  247 + bool show_xref{false};
  248 + bool show_trailer{false};
  249 + int show_obj{0};
  250 + int show_gen{0};
  251 + bool show_raw_stream_data{false};
  252 + bool show_filtered_stream_data{false};
  253 + bool show_pages{false};
  254 + bool show_page_images{false};
  255 + std::vector<size_t> collate;
  256 + bool flatten_rotation{false};
  257 + bool list_attachments{false};
  258 + std::string attachment_to_show;
  259 + std::list<std::string> attachments_to_remove;
  260 + std::list<AddAttachment> attachments_to_add;
  261 + std::list<CopyAttachmentFrom> attachments_to_copy;
  262 + int json_version{0};
  263 + std::set<std::string> json_keys;
  264 + std::set<std::string> json_objects;
  265 + qpdf_json_stream_data_e json_stream_data{qpdf_sj_none};
  266 + bool json_stream_data_set{false};
  267 + std::string json_stream_prefix;
  268 + bool test_json_schema{false};
  269 + bool check{false};
  270 + bool optimize_images{false};
  271 + bool externalize_inline_images{false};
  272 + bool keep_inline_images{false};
  273 + bool remove_info{false};
  274 + bool remove_metadata{false};
  275 + bool remove_page_labels{false};
  276 + bool remove_structure{false};
  277 + size_t oi_min_width{DEFAULT_OI_MIN_WIDTH};
  278 + size_t oi_min_height{DEFAULT_OI_MIN_HEIGHT};
  279 + size_t oi_min_area{DEFAULT_OI_MIN_AREA};
  280 + size_t ii_min_bytes{DEFAULT_II_MIN_BYTES};
  281 + std::vector<UnderOverlay> underlay;
  282 + std::vector<UnderOverlay> overlay;
  283 + UnderOverlay* under_overlay{nullptr};
  284 + Inputs inputs;
  285 + std::map<std::string, RotationSpec> rotations;
  286 + bool require_outfile{true};
  287 + bool replace_input{false};
  288 + bool check_is_encrypted{false};
  289 + bool check_requires_password{false};
  290 + bool empty_input{false};
  291 + std::string outfilename;
  292 + bool json_input{false};
  293 + bool json_output{false};
  294 + std::string update_from_json;
  295 + bool report_mem_usage{false};
  296 + std::vector<PageLabelSpec> page_label_specs;
  297 +};
  298 +
  299 +inline const char*
  300 +QPDFJob::Members::infile_nm() const
  301 +{
  302 + return inputs.infile_name().data();
  303 +}
  304 +
  305 +inline std::string const&
  306 +QPDFJob::Members::infile_name() const
  307 +{
  308 + return inputs.infile_name();
  309 +}
  310 +
  311 +#endif // QPDFJOB_PRIVATE_HH
... ...
qpdf/qpdf.testcov
... ... @@ -188,7 +188,6 @@ QPDF insert foreign page 0
188 188 QPDFWriter copy use_aes 1
189 189 QPDFParser indirect without context 0
190 190 QPDFObjectHandle trailing data in parse 0
191   -QPDFJob pages encryption password 0
192 191 QPDFTokenizer EOF reading token 0
193 192 QPDFTokenizer EOF reading appendable token 0
194 193 QPDFWriter extra header text no newline 0
... ... @@ -209,7 +208,6 @@ QPDF not caching overridden objstm object 0
209 208 QPDF_optimization indirect outlines 0
210 209 QPDF xref space 2
211 210 QPDFJob pages range omitted in middle 0
212   -QPDFJob npages 0
213 211 QPDFWriter standard deterministic ID 1
214 212 QPDFWriter linearized deterministic ID 1
215 213 qpdf-c called qpdf_set_deterministic_ID 0
... ... @@ -222,7 +220,6 @@ QPDFParser found fake 1
222 220 QPDFParser no val for last key 0
223 221 QPDF resolve failure to null 0
224 222 QPDFObjectHandle errors in parsecontent 0
225   -QPDFJob same file error 0
226 223 QPDFJob split-pages %d 0
227 224 QPDFJob split-pages .pdf 0
228 225 QPDFJob split-pages other 0
... ... @@ -286,8 +283,6 @@ QPDFAcroFormDocumentHelper non-dictionary field 0
286 283 QPDFAcroFormDocumentHelper loop 0
287 284 QPDFAcroFormDocumentHelper field found 1
288 285 QPDFAcroFormDocumentHelper annotation found 1
289   -QPDFJob keep files open n 0
290   -QPDFJob keep files open y 0
291 286 QPDFJob automatically set keep files open 1
292 287 QPDFOutlineDocumentHelper string named dest 0
293 288 QPDFObjectHandle merge top type mismatch 0
... ... @@ -338,7 +333,6 @@ QPDFPageDocumentHelper ignore annotation with no appearance 0
338 333 QPDFFormFieldObjectHelper replaced BMC at EOF 0
339 334 QPDFFormFieldObjectHelper fallback Tf 0
340 335 QPDFPageObjectHelper copy shared attribute 1
341   -QPDFJob from_nr from repeat_nr 0
342 336 QPDF resolve duplicated page object 0
343 337 QPDF handle direct page object 0
344 338 QPDF missing mediabox 0
... ... @@ -453,7 +447,6 @@ QPDFFileSpecObjectHelper empty compat_name 0
453 447 QPDFFileSpecObjectHelper non-empty compat_name 0
454 448 QPDFAcroFormDocumentHelper copy annotation 3
455 449 QPDFAcroFormDocumentHelper field with parent 3
456   -QPDFJob pages keeping field from original 0
457 450 QPDFObjectHandle merge reuse 0
458 451 QPDFObjectHandle merge generate 0
459 452 QPDFAcroFormDocumentHelper replaced DA token 0
... ... @@ -493,8 +486,6 @@ qpdf-c called qpdf_oh_get_binary_string_value 0
493 486 qpdf-c called qpdf_oh_get_binary_utf8_value 0
494 487 qpdf-c called qpdf_oh_new_binary_string 0
495 488 qpdf-c called qpdf_oh_new_binary_unicode_string 0
496   -QPDFJob duplicated pages password 0
497   -QPDFJob misplaced pages password 0
498 489 QPDFJob check encrypted encrypted 0
499 490 QPDFJob check encrypted not encrypted 0
500 491 QPDFJob check password password incorrect 0
... ... @@ -547,8 +538,6 @@ QPDFPageObjectHelper used fallback without copying 0
547 538 QPDF skipping cache for known unchecked object 0
548 539 QPDF fix dangling triggered xref reconstruction 0
549 540 QPDF recover xref stream 0
550   -QPDFJob misplaced page range 0
551   -QPDFJob duplicated range 0
552 541 QPDFJob json over/under no file 0
553 542 QPDF_Array copy 1
554 543 QPDF_json stream data not string 0
... ...
qpdf/qtest/qpdf/disable-kfo.out
1   -qpdf: selecting --keep-open-files=n
2   -qpdf: processing 001-kfo.pdf
3   -qpdf: processing 002-kfo.pdf
4   -qpdf: processing 003-kfo.pdf
5   -qpdf: processing 004-kfo.pdf
6   -qpdf: processing 005-kfo.pdf
7   -qpdf: processing 006-kfo.pdf
8   -qpdf: processing 007-kfo.pdf
9   -qpdf: processing 008-kfo.pdf
10   -qpdf: processing 009-kfo.pdf
11   -qpdf: processing 010-kfo.pdf
12   -qpdf: processing 011-kfo.pdf
13   -qpdf: processing 012-kfo.pdf
14   -qpdf: processing 013-kfo.pdf
15   -qpdf: processing 014-kfo.pdf
16   -qpdf: processing 015-kfo.pdf
17   -qpdf: processing 016-kfo.pdf
18   -qpdf: processing 017-kfo.pdf
19   -qpdf: processing 018-kfo.pdf
20   -qpdf: processing 019-kfo.pdf
21   -qpdf: processing 020-kfo.pdf
22   -qpdf: processing 021-kfo.pdf
23   -qpdf: processing 022-kfo.pdf
24   -qpdf: processing 023-kfo.pdf
25   -qpdf: processing 024-kfo.pdf
26   -qpdf: processing 025-kfo.pdf
27   -qpdf: processing 026-kfo.pdf
28   -qpdf: processing 027-kfo.pdf
29   -qpdf: processing 028-kfo.pdf
30   -qpdf: processing 029-kfo.pdf
31   -qpdf: processing 030-kfo.pdf
32   -qpdf: processing 031-kfo.pdf
33   -qpdf: processing 032-kfo.pdf
34   -qpdf: processing 033-kfo.pdf
35   -qpdf: processing 034-kfo.pdf
36   -qpdf: processing 035-kfo.pdf
37   -qpdf: processing 036-kfo.pdf
38   -qpdf: processing 037-kfo.pdf
39   -qpdf: processing 038-kfo.pdf
40   -qpdf: processing 039-kfo.pdf
41   -qpdf: processing 040-kfo.pdf
42   -qpdf: processing 041-kfo.pdf
43   -qpdf: processing 042-kfo.pdf
44   -qpdf: processing 043-kfo.pdf
45   -qpdf: processing 044-kfo.pdf
46   -qpdf: processing 045-kfo.pdf
47   -qpdf: processing 046-kfo.pdf
48   -qpdf: processing 047-kfo.pdf
49   -qpdf: processing 048-kfo.pdf
50   -qpdf: processing 049-kfo.pdf
51   -qpdf: processing 050-kfo.pdf
52   -qpdf: processing 051-kfo.pdf
53 1 qpdf: empty PDF: checking for shared resources
54 2 qpdf: no shared resources found
  3 +qpdf: selecting --keep-open-files=n
  4 +qpdf: processing 001-kfo.pdf
55 5 qpdf: 001-kfo.pdf: checking for shared resources
56 6 qpdf: no shared resources found
  7 +qpdf: processing 002-kfo.pdf
57 8 qpdf: 002-kfo.pdf: checking for shared resources
58 9 qpdf: no shared resources found
  10 +qpdf: processing 003-kfo.pdf
59 11 qpdf: 003-kfo.pdf: checking for shared resources
60 12 qpdf: no shared resources found
  13 +qpdf: processing 004-kfo.pdf
61 14 qpdf: 004-kfo.pdf: checking for shared resources
62 15 qpdf: no shared resources found
  16 +qpdf: processing 005-kfo.pdf
63 17 qpdf: 005-kfo.pdf: checking for shared resources
64 18 qpdf: no shared resources found
  19 +qpdf: processing 006-kfo.pdf
65 20 qpdf: 006-kfo.pdf: checking for shared resources
66 21 qpdf: no shared resources found
  22 +qpdf: processing 007-kfo.pdf
67 23 qpdf: 007-kfo.pdf: checking for shared resources
68 24 qpdf: no shared resources found
  25 +qpdf: processing 008-kfo.pdf
69 26 qpdf: 008-kfo.pdf: checking for shared resources
70 27 qpdf: no shared resources found
  28 +qpdf: processing 009-kfo.pdf
71 29 qpdf: 009-kfo.pdf: checking for shared resources
72 30 qpdf: no shared resources found
  31 +qpdf: processing 010-kfo.pdf
73 32 qpdf: 010-kfo.pdf: checking for shared resources
74 33 qpdf: no shared resources found
  34 +qpdf: processing 011-kfo.pdf
75 35 qpdf: 011-kfo.pdf: checking for shared resources
76 36 qpdf: no shared resources found
  37 +qpdf: processing 012-kfo.pdf
77 38 qpdf: 012-kfo.pdf: checking for shared resources
78 39 qpdf: no shared resources found
  40 +qpdf: processing 013-kfo.pdf
79 41 qpdf: 013-kfo.pdf: checking for shared resources
80 42 qpdf: no shared resources found
  43 +qpdf: processing 014-kfo.pdf
81 44 qpdf: 014-kfo.pdf: checking for shared resources
82 45 qpdf: no shared resources found
  46 +qpdf: processing 015-kfo.pdf
83 47 qpdf: 015-kfo.pdf: checking for shared resources
84 48 qpdf: no shared resources found
  49 +qpdf: processing 016-kfo.pdf
85 50 qpdf: 016-kfo.pdf: checking for shared resources
86 51 qpdf: no shared resources found
  52 +qpdf: processing 017-kfo.pdf
87 53 qpdf: 017-kfo.pdf: checking for shared resources
88 54 qpdf: no shared resources found
  55 +qpdf: processing 018-kfo.pdf
89 56 qpdf: 018-kfo.pdf: checking for shared resources
90 57 qpdf: no shared resources found
  58 +qpdf: processing 019-kfo.pdf
91 59 qpdf: 019-kfo.pdf: checking for shared resources
92 60 qpdf: no shared resources found
  61 +qpdf: processing 020-kfo.pdf
93 62 qpdf: 020-kfo.pdf: checking for shared resources
94 63 qpdf: no shared resources found
  64 +qpdf: processing 021-kfo.pdf
95 65 qpdf: 021-kfo.pdf: checking for shared resources
96 66 qpdf: no shared resources found
  67 +qpdf: processing 022-kfo.pdf
97 68 qpdf: 022-kfo.pdf: checking for shared resources
98 69 qpdf: no shared resources found
  70 +qpdf: processing 023-kfo.pdf
99 71 qpdf: 023-kfo.pdf: checking for shared resources
100 72 qpdf: no shared resources found
  73 +qpdf: processing 024-kfo.pdf
101 74 qpdf: 024-kfo.pdf: checking for shared resources
102 75 qpdf: no shared resources found
  76 +qpdf: processing 025-kfo.pdf
103 77 qpdf: 025-kfo.pdf: checking for shared resources
104 78 qpdf: no shared resources found
  79 +qpdf: processing 026-kfo.pdf
105 80 qpdf: 026-kfo.pdf: checking for shared resources
106 81 qpdf: no shared resources found
  82 +qpdf: processing 027-kfo.pdf
107 83 qpdf: 027-kfo.pdf: checking for shared resources
108 84 qpdf: no shared resources found
  85 +qpdf: processing 028-kfo.pdf
109 86 qpdf: 028-kfo.pdf: checking for shared resources
110 87 qpdf: no shared resources found
  88 +qpdf: processing 029-kfo.pdf
111 89 qpdf: 029-kfo.pdf: checking for shared resources
112 90 qpdf: no shared resources found
  91 +qpdf: processing 030-kfo.pdf
113 92 qpdf: 030-kfo.pdf: checking for shared resources
114 93 qpdf: no shared resources found
  94 +qpdf: processing 031-kfo.pdf
115 95 qpdf: 031-kfo.pdf: checking for shared resources
116 96 qpdf: no shared resources found
  97 +qpdf: processing 032-kfo.pdf
117 98 qpdf: 032-kfo.pdf: checking for shared resources
118 99 qpdf: no shared resources found
  100 +qpdf: processing 033-kfo.pdf
119 101 qpdf: 033-kfo.pdf: checking for shared resources
120 102 qpdf: no shared resources found
  103 +qpdf: processing 034-kfo.pdf
121 104 qpdf: 034-kfo.pdf: checking for shared resources
122 105 qpdf: no shared resources found
  106 +qpdf: processing 035-kfo.pdf
123 107 qpdf: 035-kfo.pdf: checking for shared resources
124 108 qpdf: no shared resources found
  109 +qpdf: processing 036-kfo.pdf
125 110 qpdf: 036-kfo.pdf: checking for shared resources
126 111 qpdf: no shared resources found
  112 +qpdf: processing 037-kfo.pdf
127 113 qpdf: 037-kfo.pdf: checking for shared resources
128 114 qpdf: no shared resources found
  115 +qpdf: processing 038-kfo.pdf
129 116 qpdf: 038-kfo.pdf: checking for shared resources
130 117 qpdf: no shared resources found
  118 +qpdf: processing 039-kfo.pdf
131 119 qpdf: 039-kfo.pdf: checking for shared resources
132 120 qpdf: no shared resources found
  121 +qpdf: processing 040-kfo.pdf
133 122 qpdf: 040-kfo.pdf: checking for shared resources
134 123 qpdf: no shared resources found
  124 +qpdf: processing 041-kfo.pdf
135 125 qpdf: 041-kfo.pdf: checking for shared resources
136 126 qpdf: no shared resources found
  127 +qpdf: processing 042-kfo.pdf
137 128 qpdf: 042-kfo.pdf: checking for shared resources
138 129 qpdf: no shared resources found
  130 +qpdf: processing 043-kfo.pdf
139 131 qpdf: 043-kfo.pdf: checking for shared resources
140 132 qpdf: no shared resources found
  133 +qpdf: processing 044-kfo.pdf
141 134 qpdf: 044-kfo.pdf: checking for shared resources
142 135 qpdf: no shared resources found
  136 +qpdf: processing 045-kfo.pdf
143 137 qpdf: 045-kfo.pdf: checking for shared resources
144 138 qpdf: no shared resources found
  139 +qpdf: processing 046-kfo.pdf
145 140 qpdf: 046-kfo.pdf: checking for shared resources
146 141 qpdf: no shared resources found
  142 +qpdf: processing 047-kfo.pdf
147 143 qpdf: 047-kfo.pdf: checking for shared resources
148 144 qpdf: no shared resources found
  145 +qpdf: processing 048-kfo.pdf
149 146 qpdf: 048-kfo.pdf: checking for shared resources
150 147 qpdf: no shared resources found
  148 +qpdf: processing 049-kfo.pdf
151 149 qpdf: 049-kfo.pdf: checking for shared resources
152 150 qpdf: no shared resources found
  151 +qpdf: processing 050-kfo.pdf
153 152 qpdf: 050-kfo.pdf: checking for shared resources
154 153 qpdf: no shared resources found
  154 +qpdf: processing 051-kfo.pdf
155 155 qpdf: 051-kfo.pdf: checking for shared resources
156 156 qpdf: no shared resources found
157 157 qpdf: removing unreferenced pages from primary input
... ...
qpdf/qtest/qpdf/enable-kfo.out
1   -qpdf: selecting --keep-open-files=y
2   -qpdf: processing 010-kfo.pdf
3   -qpdf: processing 011-kfo.pdf
4   -qpdf: processing 012-kfo.pdf
5   -qpdf: processing 013-kfo.pdf
6   -qpdf: processing 014-kfo.pdf
7   -qpdf: processing 015-kfo.pdf
8   -qpdf: processing 016-kfo.pdf
9   -qpdf: processing 017-kfo.pdf
10   -qpdf: processing 018-kfo.pdf
11   -qpdf: processing 019-kfo.pdf
12 1 qpdf: empty PDF: checking for shared resources
13 2 qpdf: no shared resources found
  3 +qpdf: selecting --keep-open-files=y
  4 +qpdf: processing 010-kfo.pdf
14 5 qpdf: 010-kfo.pdf: checking for shared resources
15 6 qpdf: no shared resources found
  7 +qpdf: processing 011-kfo.pdf
16 8 qpdf: 011-kfo.pdf: checking for shared resources
17 9 qpdf: no shared resources found
  10 +qpdf: processing 012-kfo.pdf
18 11 qpdf: 012-kfo.pdf: checking for shared resources
19 12 qpdf: no shared resources found
  13 +qpdf: processing 013-kfo.pdf
20 14 qpdf: 013-kfo.pdf: checking for shared resources
21 15 qpdf: no shared resources found
  16 +qpdf: processing 014-kfo.pdf
22 17 qpdf: 014-kfo.pdf: checking for shared resources
23 18 qpdf: no shared resources found
  19 +qpdf: processing 015-kfo.pdf
24 20 qpdf: 015-kfo.pdf: checking for shared resources
25 21 qpdf: no shared resources found
  22 +qpdf: processing 016-kfo.pdf
26 23 qpdf: 016-kfo.pdf: checking for shared resources
27 24 qpdf: no shared resources found
  25 +qpdf: processing 017-kfo.pdf
28 26 qpdf: 017-kfo.pdf: checking for shared resources
29 27 qpdf: no shared resources found
  28 +qpdf: processing 018-kfo.pdf
30 29 qpdf: 018-kfo.pdf: checking for shared resources
31 30 qpdf: no shared resources found
  31 +qpdf: processing 019-kfo.pdf
32 32 qpdf: 019-kfo.pdf: checking for shared resources
33 33 qpdf: no shared resources found
34 34 qpdf: removing unreferenced pages from primary input
... ...
qpdf/qtest/qpdf/kfo-n.out
1   -qpdf: processing 001-kfo.pdf
2   -qpdf: processing 002-kfo.pdf
3   -qpdf: processing 003-kfo.pdf
4   -qpdf: processing 004-kfo.pdf
5   -qpdf: processing 005-kfo.pdf
6   -qpdf: processing 006-kfo.pdf
7   -qpdf: processing 007-kfo.pdf
8   -qpdf: processing 008-kfo.pdf
9   -qpdf: processing 009-kfo.pdf
10 1 qpdf: empty PDF: checking for shared resources
11 2 qpdf: no shared resources found
  3 +qpdf: processing 001-kfo.pdf
12 4 qpdf: 001-kfo.pdf: checking for shared resources
13 5 qpdf: no shared resources found
  6 +qpdf: processing 002-kfo.pdf
14 7 qpdf: 002-kfo.pdf: checking for shared resources
15 8 qpdf: no shared resources found
  9 +qpdf: processing 003-kfo.pdf
16 10 qpdf: 003-kfo.pdf: checking for shared resources
17 11 qpdf: no shared resources found
  12 +qpdf: processing 004-kfo.pdf
18 13 qpdf: 004-kfo.pdf: checking for shared resources
19 14 qpdf: no shared resources found
  15 +qpdf: processing 005-kfo.pdf
20 16 qpdf: 005-kfo.pdf: checking for shared resources
21 17 qpdf: no shared resources found
  18 +qpdf: processing 006-kfo.pdf
22 19 qpdf: 006-kfo.pdf: checking for shared resources
23 20 qpdf: no shared resources found
  21 +qpdf: processing 007-kfo.pdf
24 22 qpdf: 007-kfo.pdf: checking for shared resources
25 23 qpdf: no shared resources found
  24 +qpdf: processing 008-kfo.pdf
26 25 qpdf: 008-kfo.pdf: checking for shared resources
27 26 qpdf: no shared resources found
  27 +qpdf: processing 009-kfo.pdf
28 28 qpdf: 009-kfo.pdf: checking for shared resources
29 29 qpdf: no shared resources found
30 30 qpdf: removing unreferenced pages from primary input
... ...
qpdf/qtest/qpdf/kfo-y.out
1   -qpdf: processing 001-kfo.pdf
2   -qpdf: processing 002-kfo.pdf
3   -qpdf: processing 003-kfo.pdf
4   -qpdf: processing 004-kfo.pdf
5   -qpdf: processing 005-kfo.pdf
6   -qpdf: processing 006-kfo.pdf
7   -qpdf: processing 007-kfo.pdf
8   -qpdf: processing 008-kfo.pdf
9   -qpdf: processing 009-kfo.pdf
10 1 qpdf: empty PDF: checking for shared resources
11 2 qpdf: no shared resources found
  3 +qpdf: processing 001-kfo.pdf
12 4 qpdf: 001-kfo.pdf: checking for shared resources
13 5 qpdf: no shared resources found
  6 +qpdf: processing 002-kfo.pdf
14 7 qpdf: 002-kfo.pdf: checking for shared resources
15 8 qpdf: no shared resources found
  9 +qpdf: processing 003-kfo.pdf
16 10 qpdf: 003-kfo.pdf: checking for shared resources
17 11 qpdf: no shared resources found
  12 +qpdf: processing 004-kfo.pdf
18 13 qpdf: 004-kfo.pdf: checking for shared resources
19 14 qpdf: no shared resources found
  15 +qpdf: processing 005-kfo.pdf
20 16 qpdf: 005-kfo.pdf: checking for shared resources
21 17 qpdf: no shared resources found
  18 +qpdf: processing 006-kfo.pdf
22 19 qpdf: 006-kfo.pdf: checking for shared resources
23 20 qpdf: no shared resources found
  21 +qpdf: processing 007-kfo.pdf
24 22 qpdf: 007-kfo.pdf: checking for shared resources
25 23 qpdf: no shared resources found
  24 +qpdf: processing 008-kfo.pdf
26 25 qpdf: 008-kfo.pdf: checking for shared resources
27 26 qpdf: no shared resources found
  27 +qpdf: processing 009-kfo.pdf
28 28 qpdf: 009-kfo.pdf: checking for shared resources
29 29 qpdf: no shared resources found
30 30 qpdf: removing unreferenced pages from primary input
... ...
qpdf/qtest/qpdf/uo-6.out
1   -qpdf: selecting --keep-open-files=y
2 1 qpdf: fxo-red.pdf: checking for shared resources
3 2 qpdf: no shared resources found
  3 +qpdf: selecting --keep-open-files=y
4 4 qpdf: removing unreferenced pages from primary input
5 5 qpdf: adding pages from fxo-red.pdf
6 6 qpdf: processing underlay/overlay
... ...
qpdf/qtest/qpdf/uo-8.out
1   -qpdf: selecting --keep-open-files=y
2 1 qpdf: fxo-red.pdf: checking for shared resources
3 2 qpdf: no shared resources found
  3 +qpdf: selecting --keep-open-files=y
4 4 qpdf: removing unreferenced pages from primary input
5 5 qpdf: adding pages from fxo-red.pdf
6 6 qpdf: processing underlay/overlay
... ...
qpdf/qtest/qpdf/verbose-merge.out
  1 +qpdf: page-labels-and-outlines.pdf: checking for shared resources
  2 +qpdf: no shared resources found
1 3 qpdf: selecting --keep-open-files=y
2   -qpdf: processing 20-pages.pdf
3 4 qpdf: processing ./20-pages.pdf
4   -qpdf: processing minimal.pdf
5 5 qpdf: ./20-pages.pdf: checking for shared resources
6 6 qpdf: no shared resources found
  7 +qpdf: processing 20-pages.pdf
7 8 qpdf: 20-pages.pdf: checking for shared resources
8 9 qpdf: no shared resources found
  10 +qpdf: processing minimal.pdf
9 11 qpdf: minimal.pdf: checking for shared resources
10 12 qpdf: no shared resources found
11   -qpdf: page-labels-and-outlines.pdf: checking for shared resources
12   -qpdf: no shared resources found
13 13 qpdf: removing unreferenced pages from primary input
14 14 qpdf: adding pages from page-labels-and-outlines.pdf
15 15 qpdf: adding pages from 20-pages.pdf
... ...