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,16 +156,6 @@ class QPDFJob
156 bool replace{false}; 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 public: 159 public:
170 // CONFIGURATION 160 // CONFIGURATION
171 161
@@ -425,58 +415,16 @@ class QPDFJob @@ -425,58 +415,16 @@ class QPDFJob
425 [[deprecated("use job_json_schema(version)")]] static std::string QPDF_DLL job_json_schema_v1(); 415 [[deprecated("use job_json_schema(version)")]] static std::string QPDF_DLL job_json_schema_v1();
426 416
427 private: 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 enum password_mode_e { pm_bytes, pm_hex_bytes, pm_unicode, pm_auto }; 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 // Helper functions 428 // Helper functions
481 static void usage(std::string const& msg); 429 static void usage(std::string const& msg);
482 static JSON json_schema(int json_version, std::set<std::string>* keys = nullptr); 430 static JSON json_schema(int json_version, std::set<std::string>* keys = nullptr);
@@ -513,20 +461,19 @@ class QPDFJob @@ -513,20 +461,19 @@ class QPDFJob
513 461
514 // Transformations 462 // Transformations
515 void setQPDFOptions(QPDF& pdf); 463 void setQPDFOptions(QPDF& pdf);
516 - void handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_heap); 464 + void handlePageSpecs(QPDF& pdf);
517 bool shouldRemoveUnreferencedResources(QPDF& pdf); 465 bool shouldRemoveUnreferencedResources(QPDF& pdf);
518 void handleRotations(QPDF& pdf); 466 void handleRotations(QPDF& pdf);
519 void getUOPagenos( 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 void handleUnderOverlay(QPDF& pdf); 469 void handleUnderOverlay(QPDF& pdf);
522 std::string doUnderOverlayForPage( 470 std::string doUnderOverlayForPage(
523 QPDF& pdf, 471 QPDF& pdf,
524 UnderOverlay& uo, 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 size_t uo_idx, 475 size_t uo_idx,
528 std::map<int, std::map<size_t, QPDFObjectHandle>>& fo, 476 std::map<int, std::map<size_t, QPDFObjectHandle>>& fo,
529 - std::vector<QPDFPageObjectHelper>& pages,  
530 QPDFPageObjectHelper& dest_page); 477 QPDFPageObjectHelper& dest_page);
531 void validateUnderOverlay(QPDF& pdf, UnderOverlay* uo); 478 void validateUnderOverlay(QPDF& pdf, UnderOverlay* uo);
532 void handleTransformations(QPDF& pdf); 479 void handleTransformations(QPDF& pdf);
@@ -568,159 +515,8 @@ class QPDFJob @@ -568,159 +515,8 @@ class QPDFJob
568 515
569 enum remove_unref_e { re_auto, re_yes, re_no }; 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 std::shared_ptr<Members> m; 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 #include <cstring> 3 #include <cstring>
4 #include <iostream> 4 #include <iostream>
@@ -58,18 +58,7 @@ namespace @@ -58,18 +58,7 @@ namespace
58 std::shared_ptr<Pl_DCT::CompressConfig> config; 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 public: 63 public:
75 ProgressReporter(Pipeline& p, std::string const& prefix, char const* filename) : 64 ProgressReporter(Pipeline& p, std::string const& prefix, char const* filename) :
@@ -78,8 +67,12 @@ namespace @@ -78,8 +67,12 @@ namespace
78 filename(filename) 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 private: 77 private:
85 Pipeline& p; 78 Pipeline& p;
@@ -239,47 +232,32 @@ ImageOptimizer::provideStreamData(QPDFObjGen const&amp;, Pipeline* pipeline) @@ -239,47 +232,32 @@ ImageOptimizer::provideStreamData(QPDFObjGen const&amp;, Pipeline* pipeline)
239 image.pipeStreamData(p.get(), 0, decode_level, false, false); 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 QPDFJob::QPDFJob() : 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,7 +394,7 @@ QPDFJob::createQPDF()
416 checkConfiguration(); 394 checkConfiguration();
417 std::unique_ptr<QPDF> pdf_sp; 395 std::unique_ptr<QPDF> pdf_sp;
418 try { 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 } catch (QPDFExc& e) { 398 } catch (QPDFExc& e) {
421 if (e.getErrorCode() == qpdf_e_password) { 399 if (e.getErrorCode() == qpdf_e_password) {
422 // Allow certain operations to work when an incorrect password is supplied. 400 // Allow certain operations to work when an incorrect password is supplied.
@@ -447,40 +425,34 @@ QPDFJob::createQPDF() @@ -447,40 +425,34 @@ QPDFJob::createQPDF()
447 pdf.updateFromJSON(m->update_from_json); 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 if (!m->rotations.empty()) { 429 if (!m->rotations.empty()) {
455 handleRotations(pdf); 430 handleRotations(pdf);
456 } 431 }
457 handleUnderOverlay(pdf); 432 handleUnderOverlay(pdf);
458 handleTransformations(pdf); 433 handleTransformations(pdf);
  434 + m->warnings |= m->inputs.clear();
  435 +
  436 + auto root = pdf.getRoot();
459 if (m->remove_info) { 437 if (m->remove_info) {
460 auto trailer = pdf.getTrailer(); 438 auto trailer = pdf.getTrailer();
461 - auto mod_date = trailer.getKey("/Info").getKeyIfDict("/ModDate"); 439 + auto mod_date = trailer["/Info"]["/ModDate"];
462 if (mod_date.null()) { 440 if (mod_date.null()) {
463 - trailer.removeKey("/Info"); 441 + trailer.erase("/Info");
464 } else { 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 if (m->remove_metadata) { 448 if (m->remove_metadata) {
472 - pdf.getRoot().removeKey("/Metadata"); 449 + root.erase("/Metadata");
473 } 450 }
474 if (m->remove_structure) { 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 return pdf_sp; 456 return pdf_sp;
485 } 457 }
486 458
@@ -595,10 +567,10 @@ QPDFJob::checkConfiguration() @@ -595,10 +567,10 @@ QPDFJob::checkConfiguration()
595 // standard output. 567 // standard output.
596 m->outfilename = "-"; 568 m->outfilename = "-";
597 } 569 }
598 - if (m->infilename.empty() && !m->empty_input) { 570 + if (m->infile_name().empty() && !m->empty_input) {
599 usage("an input file name is required"); 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 usage("--replace-input may not be used with --empty"); 574 usage("--replace-input may not be used with --empty");
603 } 575 }
604 if (m->require_outfile && m->outfilename.empty() && !m->replace_input) { 576 if (m->require_outfile && m->outfilename.empty() && !m->replace_input) {
@@ -637,8 +609,7 @@ QPDFJob::checkConfiguration() @@ -637,8 +609,7 @@ QPDFJob::checkConfiguration()
637 if (save_to_stdout) { 609 if (save_to_stdout) {
638 m->log->saveToStandardOutput(true); 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 usage( 613 usage(
643 "input file and output file are the same; use --replace-input to intentionally " 614 "input file and output file are the same; use --replace-input to intentionally "
644 "overwrite the input file"); 615 "overwrite the input file");
@@ -762,7 +733,7 @@ QPDFJob::doCheck(QPDF&amp; pdf) @@ -762,7 +733,7 @@ QPDFJob::doCheck(QPDF&amp; pdf)
762 // may continue to perform additional checks after finding errors. 733 // may continue to perform additional checks after finding errors.
763 bool okay = true; 734 bool okay = true;
764 auto& cout = *m->log->getInfo(); 735 auto& cout = *m->log->getInfo();
765 - cout << "checking " << m->infilename << "\n"; 736 + cout << "checking " << m->infile_name() << "\n";
766 QPDF::JobSetter::setCheckMode(pdf, true); 737 QPDF::JobSetter::setCheckMode(pdf, true);
767 try { 738 try {
768 int extension_level = pdf.getExtensionLevel(); 739 int extension_level = pdf.getExtensionLevel();
@@ -933,7 +904,7 @@ QPDFJob::doListAttachments(QPDF&amp; pdf) @@ -933,7 +904,7 @@ QPDFJob::doListAttachments(QPDF&amp; pdf)
933 }); 904 });
934 } 905 }
935 } else { 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,7 +1670,6 @@ QPDFJob::doInspection(QPDF&amp; pdf)
1699 doCheck(pdf); 1670 doCheck(pdf);
1700 } 1671 }
1701 if (m->show_npages) { 1672 if (m->show_npages) {
1702 - QTC::TC("qpdf", "QPDFJob npages");  
1703 cout << pdf.getRoot().getKey("/Pages").getKey("/Count").getIntValue() << "\n"; 1673 cout << pdf.getRoot().getKey("/Pages").getKey("/Count").getIntValue() << "\n";
1704 } 1674 }
1705 if (m->show_encryption) { 1675 if (m->show_encryption) {
@@ -1707,9 +1677,9 @@ QPDFJob::doInspection(QPDF&amp; pdf) @@ -1707,9 +1677,9 @@ QPDFJob::doInspection(QPDF&amp; pdf)
1707 } 1677 }
1708 if (m->check_linearization) { 1678 if (m->check_linearization) {
1709 if (!pdf.isLinearized()) { 1679 if (!pdf.isLinearized()) {
1710 - cout << m->infilename << " is not linearized\n"; 1680 + cout << m->infile_name() << " is not linearized\n";
1711 } else if (pdf.checkLinearization()) { 1681 } else if (pdf.checkLinearization()) {
1712 - cout << m->infilename << ": no linearization errors\n"; 1682 + cout << m->infile_name() << ": no linearization errors\n";
1713 } else { 1683 } else {
1714 m->warnings = true; 1684 m->warnings = true;
1715 } 1685 }
@@ -1718,7 +1688,7 @@ QPDFJob::doInspection(QPDF&amp; pdf) @@ -1718,7 +1688,7 @@ QPDFJob::doInspection(QPDF&amp; pdf)
1718 if (pdf.isLinearized()) { 1688 if (pdf.isLinearized()) {
1719 pdf.showLinearizationData(); 1689 pdf.showLinearizationData();
1720 } else { 1690 } else {
1721 - cout << m->infilename << " is not linearized\n"; 1691 + cout << m->infile_name() << " is not linearized\n";
1722 } 1692 }
1723 } 1693 }
1724 if (m->show_xref) { 1694 if (m->show_xref) {
@@ -1755,7 +1725,7 @@ QPDFJob::doProcessOnce( @@ -1755,7 +1725,7 @@ QPDFJob::doProcessOnce(
1755 if (empty) { 1725 if (empty) {
1756 pdf->emptyPDF(); 1726 pdf->emptyPDF();
1757 } else if (main_input && m->json_input) { 1727 } else if (main_input && m->json_input) {
1758 - pdf->createFromJSON(m->infilename); 1728 + pdf->createFromJSON(m->infile_name());
1759 } else { 1729 } else {
1760 fn(pdf.get(), password); 1730 fn(pdf.get(), password);
1761 } 1731 }
@@ -1867,25 +1837,22 @@ QPDFJob::processInputSource( @@ -1867,25 +1837,22 @@ QPDFJob::processInputSource(
1867 void 1837 void
1868 QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) 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 processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false); 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 try { 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 } catch (std::runtime_error& e) { 1844 } catch (std::runtime_error& e) {
1878 throw std::runtime_error( 1845 throw std::runtime_error(
1879 "parsing numeric range for " + uo->which + " \"to\" pages: " + e.what()); 1846 "parsing numeric range for " + uo->which + " \"to\" pages: " + e.what());
1880 } 1847 }
1881 try { 1848 try {
1882 if (uo->from_nr.empty()) { 1849 if (uo->from_nr.empty()) {
1883 - QTC::TC("qpdf", "QPDFJob from_nr from repeat_nr");  
1884 uo->from_nr = uo->repeat_nr; 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 if (!uo->repeat_nr.empty()) { 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 } catch (std::runtime_error& e) { 1857 } catch (std::runtime_error& e) {
1891 throw std::runtime_error( 1858 throw std::runtime_error(
@@ -1897,29 +1864,28 @@ std::string @@ -1897,29 +1864,28 @@ std::string
1897 QPDFJob::doUnderOverlayForPage( 1864 QPDFJob::doUnderOverlayForPage(
1898 QPDF& pdf, 1865 QPDF& pdf,
1899 UnderOverlay& uo, 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 size_t uo_idx, 1869 size_t uo_idx,
1903 std::map<int, std::map<size_t, QPDFObjectHandle>>& fo, 1870 std::map<int, std::map<size_t, QPDFObjectHandle>>& fo,
1904 - std::vector<QPDFPageObjectHelper>& pages,  
1905 QPDFPageObjectHelper& dest_page) 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 return ""; 1874 return "";
1910 } 1875 }
1911 auto& dest_afdh = dest_page.qpdf()->acroform(); 1876 auto& dest_afdh = dest_page.qpdf()->acroform();
1912 1877
  1878 + auto const& pages = uo.pdf->getAllPages();
1913 std::string content; 1879 std::string content;
1914 int min_suffix = 1; 1880 int min_suffix = 1;
1915 QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true); 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 doIfVerbose([&](Pipeline& v, std::string const& prefix) { 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 // If the same page is overlaid or underlaid multiple times, we'll generate multiple names 1891 // If the same page is overlaid or underlaid multiple times, we'll generate multiple names
@@ -1927,13 +1893,13 @@ QPDFJob::doUnderOverlayForPage( @@ -1927,13 +1893,13 @@ QPDFJob::doUnderOverlayForPage(
1927 std::string name = resources.getUniqueResourceName("/Fx", min_suffix); 1893 std::string name = resources.getUniqueResourceName("/Fx", min_suffix);
1928 QPDFMatrix cm; 1894 QPDFMatrix cm;
1929 std::string new_content = dest_page.placeFormXObject( 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 dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->acroform()); 1897 dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->acroform());
1932 if (!new_content.empty()) { 1898 if (!new_content.empty()) {
1933 resources.mergeResources("<< /XObject << >> >>"_qpdf); 1899 resources.mergeResources("<< /XObject << >> >>"_qpdf);
1934 auto xobject = resources.getKey("/XObject"); 1900 auto xobject = resources.getKey("/XObject");
1935 if (xobject.isDictionary()) { 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 ++min_suffix; 1904 ++min_suffix;
1939 content += new_content; 1905 content += new_content;
@@ -1945,14 +1911,15 @@ QPDFJob::doUnderOverlayForPage( @@ -1945,14 +1911,15 @@ QPDFJob::doUnderOverlayForPage(
1945 void 1911 void
1946 QPDFJob::getUOPagenos( 1912 QPDFJob::getUOPagenos(
1947 std::vector<QPDFJob::UnderOverlay>& uos, 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 size_t uo_idx = 0; 1916 size_t uo_idx = 0;
1951 for (auto const& uo: uos) { 1917 for (auto const& uo: uos) {
1952 size_t page_idx = 0; 1918 size_t page_idx = 0;
1953 size_t from_size = uo.from_pagenos.size(); 1919 size_t from_size = uo.from_pagenos.size();
1954 size_t repeat_size = uo.repeat_pagenos.size(); 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 if (page_idx < from_size) { 1923 if (page_idx < from_size) {
1957 pagenos[to_pageno][uo_idx].push_back(uo.from_pagenos.at(page_idx)); 1924 pagenos[to_pageno][uo_idx].push_back(uo.from_pagenos.at(page_idx));
1958 } else if (repeat_size) { 1925 } else if (repeat_size) {
@@ -1978,67 +1945,47 @@ QPDFJob::handleUnderOverlay(QPDF&amp; pdf) @@ -1978,67 +1945,47 @@ QPDFJob::handleUnderOverlay(QPDF&amp; pdf)
1978 validateUnderOverlay(pdf, &uo); 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 getUOPagenos(m->underlay, underlay_pagenos); 1954 getUOPagenos(m->underlay, underlay_pagenos);
1986 getUOPagenos(m->overlay, overlay_pagenos); 1955 getUOPagenos(m->overlay, overlay_pagenos);
1987 doIfVerbose([&](Pipeline& v, std::string const& prefix) { 1956 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
1988 v << prefix << ": processing underlay/overlay\n"; 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 std::map<int, std::map<size_t, QPDFObjectHandle>> underlay_fo; 1960 std::map<int, std::map<size_t, QPDFObjectHandle>> underlay_fo;
2005 std::map<int, std::map<size_t, QPDFObjectHandle>> overlay_fo; 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 continue; 1970 continue;
2015 } 1971 }
2016 // This code converts the original page, any underlays, and any overlays to form XObjects. 1972 // This code converts the original page, any underlays, and any overlays to form XObjects.
2017 // Then it concatenates display of all underlays, the original page, and all overlays. Prior 1973 // Then it concatenates display of all underlays, the original page, and all overlays. Prior
2018 // to 11.3.0, the original page contents were wrapped in q/Q, but this didn't work if the 1974 // to 11.3.0, the original page contents were wrapped in q/Q, but this didn't work if the
2019 // original page had unbalanced q/Q operators. See GitHub issue #904. 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 auto this_page_fo = dest_page.getFormXObjectForPage(); 1976 auto this_page_fo = dest_page.getFormXObjectForPage();
2023 // The resulting form xobject lazily reads the content from the original page, which we are 1977 // The resulting form xobject lazily reads the content from the original page, which we are
2024 // going to replace. Therefore, we have to explicitly copy it. 1978 // going to replace. Therefore, we have to explicitly copy it.
2025 auto content_data = this_page_fo.getRawStreamData(); 1979 auto content_data = this_page_fo.getRawStreamData();
2026 this_page_fo.replaceStreamData(content_data, QPDFObjectHandle(), QPDFObjectHandle()); 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 size_t uo_idx{0}; 1984 size_t uo_idx{0};
2031 std::string content; 1985 std::string content;
2032 for (auto& underlay: m->underlay) { 1986 for (auto& underlay: m->underlay) {
2033 content += doUnderOverlayForPage( 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 ++uo_idx; 1989 ++uo_idx;
2043 } 1990 }
2044 content += dest_page.placeFormXObject( 1991 content += dest_page.placeFormXObject(
@@ -2051,17 +1998,11 @@ QPDFJob::handleUnderOverlay(QPDF&amp; pdf) @@ -2051,17 +1998,11 @@ QPDFJob::handleUnderOverlay(QPDF&amp; pdf)
2051 uo_idx = 0; 1998 uo_idx = 0;
2052 for (auto& overlay: m->overlay) { 1999 for (auto& overlay: m->overlay) {
2053 content += doUnderOverlayForPage( 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 ++uo_idx; 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,108 +2330,200 @@ added_page(QPDF&amp; pdf, QPDFPageObjectHelper page)
2389 return added_page(pdf, page.getObjectHandle()); 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 void 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 // Count the number of distinct files to determine whether we should keep files open or not. 2416 // Count the number of distinct files to determine whether we should keep files open or not.
2409 // Rather than trying to code some portable heuristic based on OS limits, just hard-code 2417 // Rather than trying to code some portable heuristic based on OS limits, just hard-code
2410 // this at a given number and allow users to override. 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 << "\n"; 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 // Clear all pages out of the primary QPDF's pages tree but leave the objects in place in the 2528 // Clear all pages out of the primary QPDF's pages tree but leave the objects in place in the
2496 // file so they can be re-added without changing their object numbers. This enables other things 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,23 +2531,22 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2498 doIfVerbose([&](Pipeline& v, std::string const& prefix) { 2531 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
2499 v << prefix << ": removing unreferenced pages from primary input\n"; 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 auto n_collate = m->collate.size(); 2538 auto n_collate = m->collate.size();
2508 - auto n_specs = parsed_specs.size(); 2539 + auto n_specs = m->inputs.selections.size();
2509 if (!(n_collate == 0 || n_collate == 1 || n_collate == n_specs)) { 2540 if (!(n_collate == 0 || n_collate == 1 || n_collate == n_specs)) {
2510 usage( 2541 usage(
2511 "--pages: if --collate has more than one value, it must have one value per page " 2542 "--pages: if --collate has more than one value, it must have one value per page "
2512 "specification"); 2543 "specification");
2513 } 2544 }
  2545 +
  2546 + std::vector<Selection> new_specs;
2514 if (n_collate > 0 && n_specs > 1) { 2547 if (n_collate > 0 && n_specs > 1) {
2515 // Collate the pages by selecting one page from each spec in order. When a spec runs out of 2548 // Collate the pages by selecting one page from each spec in order. When a spec runs out of
2516 // pages, stop selecting from it. 2549 // pages, stop selecting from it.
2517 - std::vector<QPDFPageData> new_parsed_specs;  
2518 // Make sure we have a collate value for each spec. We have already checked that a non-empty 2550 // Make sure we have a collate value for each spec. We have already checked that a non-empty
2519 // collate has either one value or one value per spec. 2551 // collate has either one value or one value per spec.
2520 for (auto i = n_collate; i < n_specs; ++i) { 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,70 +2557,55 @@ QPDFJob::handlePageSpecs(QPDF&amp; pdf, std::vector&lt;std::unique_ptr&lt;QPDF&gt;&gt;&amp; page_hea
2525 while (got_pages) { 2557 while (got_pages) {
2526 got_pages = false; 2558 got_pages = false;
2527 for (size_t i = 0; i < n_specs; ++i) { 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 for (size_t j = 0; j < m->collate.at(i); ++j) { 2561 for (size_t j = 0; j < m->collate.at(i); ++j) {
2530 if (cur_page.at(i) + j < page_data.selected_pages.size()) { 2562 if (cur_page.at(i) + j < page_data.selected_pages.size()) {
2531 got_pages = true; 2563 got_pages = true;
2532 - new_parsed_specs.emplace_back( 2564 + new_specs.emplace_back(
2533 page_data, page_data.selected_pages.at(cur_page.at(i) + j)); 2565 page_data, page_data.selected_pages.at(cur_page.at(i) + j));
2534 } 2566 }
2535 } 2567 }
2536 cur_page.at(i) += m->collate.at(i); 2568 cur_page.at(i) += m->collate.at(i);
2537 } 2569 }
2538 } 2570 }
2539 - parsed_specs = new_parsed_specs;  
2540 } 2571 }
2541 2572
2542 // Add all the pages from all the files in the order specified. Keep track of any pages from the 2573 // Add all the pages from all the files in the order specified. Keep track of any pages from the
2543 // original file that we are selecting. 2574 // original file that we are selecting.
2544 - std::set<int> selected_from_orig;  
2545 std::vector<QPDFObjectHandle> new_labels; 2575 std::vector<QPDFObjectHandle> new_labels;
2546 - bool any_page_labels = false;  
2547 int out_pageno = 0; 2576 int out_pageno = 0;
2548 auto& this_afdh = pdf.acroform(); 2577 auto& this_afdh = pdf.acroform();
2549 std::set<QPDFObjGen> referenced_fields; 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 doIfVerbose([&](Pipeline& v, std::string const& prefix) { 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 // Pages are specified from 1 but numbered from 0 in the vector 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 to_copy = to_copy.shallowCopyPage(); 2601 to_copy = to_copy.shallowCopyPage();
2577 } else { 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 to_copy.removeUnreferencedResources(); 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 auto new_page = added_page(pdf, to_copy); 2609 auto new_page = added_page(pdf, to_copy);
2593 // Try to avoid gratuitously renaming fields. In the case of where we're just extracting 2610 // Try to avoid gratuitously renaming fields. In the case of where we're just extracting
2594 // a bunch of pages from the original file and not copying any page more than once, 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,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 // Delete page objects for unused page in primary. This prevents those objects from being 2644 // Delete page objects for unused page in primary. This prevents those objects from being
2630 // preserved by being referred to from other places, such as the outlines dictionary. Also make 2645 // preserved by being referred to from other places, such as the outlines dictionary. Also make
2631 // sure we keep form fields from pages we preserved. 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 for (auto field: this_afdh.getFormFieldsForPage(page)) { 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 } else { 2653 } else {
2640 - pdf.replaceObject(page.getObjectHandle().getObjGen(), QPDFObjectHandle::newNull()); 2654 + pdf.replaceObject(page, QPDFObjectHandle::newNull());
2641 } 2655 }
  2656 + ++page_idx;
2642 } 2657 }
2643 // Remove unreferenced form fields 2658 // Remove unreferenced form fields
2644 if (this_afdh.hasAcroForm()) { 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 if (referenced_fields.contains(field.getObjGen())) { 2665 if (referenced_fields.contains(field.getObjGen())) {
2654 - new_fields.appendItem(field); 2666 + new_fields.emplace_back(field);
2655 } 2667 }
2656 } 2668 }
2657 if (new_fields.empty()) { 2669 if (new_fields.empty()) {
2658 - pdf.getRoot().removeKey("/AcroForm"); 2670 + pdf.getRoot().erase("/AcroForm");
2659 } else { 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,8 +2939,8 @@ QPDFJob::setWriterOptions(QPDFWriter&amp; w)
2924 std::unique_ptr<QPDF> encryption_pdf; 2939 std::unique_ptr<QPDF> encryption_pdf;
2925 processFile( 2940 processFile(
2926 encryption_pdf, 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 false, 2944 false,
2930 false); 2945 false);
2931 w.copyEncryptionParameters(*encryption_pdf); 2946 w.copyEncryptionParameters(*encryption_pdf);
@@ -3045,7 +3060,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf) @@ -3045,7 +3060,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3045 page_range += "-" + QUtil::uint_to_string(last, QIntC::to_int(pageno_len)); 3060 page_range += "-" + QUtil::uint_to_string(last, QIntC::to_int(pageno_len));
3046 } 3061 }
3047 std::string outfile = before + page_range + after; 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 throw std::runtime_error("split pages would overwrite input file with " + outfile); 3064 throw std::runtime_error("split pages would overwrite input file with " + outfile);
3050 } 3065 }
3051 QPDFWriter w(outpdf, outfile.c_str()); 3066 QPDFWriter w(outpdf, outfile.c_str());
@@ -3064,7 +3079,7 @@ QPDFJob::writeOutfile(QPDF&amp; pdf) @@ -3064,7 +3079,7 @@ QPDFJob::writeOutfile(QPDF&amp; pdf)
3064 if (m->replace_input) { 3079 if (m->replace_input) {
3065 // Append but don't prepend to the path to generate a temporary name. This saves us from 3080 // Append but don't prepend to the path to generate a temporary name. This saves us from
3066 // having to split the path by directory and non-directory. 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 // m->outfilename will be restored to 0 before temp_out goes out of scope. 3083 // m->outfilename will be restored to 0 before temp_out goes out of scope.
3069 m->outfilename = temp_out; 3084 m->outfilename = temp_out;
3070 } else if (m->outfilename == "-") { 3085 } else if (m->outfilename == "-") {
@@ -3098,13 +3113,13 @@ QPDFJob::writeOutfile(QPDF&amp; pdf) @@ -3098,13 +3113,13 @@ QPDFJob::writeOutfile(QPDF&amp; pdf)
3098 if (m->replace_input) { 3113 if (m->replace_input) {
3099 // We must close the input before we can rename files 3114 // We must close the input before we can rename files
3100 pdf.closeInputSource(); 3115 pdf.closeInputSource();
3101 - std::string backup = m->infilename + ".~qpdf-orig"; 3116 + std::string backup = m->infile_name() + ".~qpdf-orig";
3102 bool warnings = pdf.anyWarnings(); 3117 bool warnings = pdf.anyWarnings();
3103 if (!warnings) { 3118 if (!warnings) {
3104 backup.append(1, '#'); 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 if (warnings) { 3123 if (warnings) {
3109 *m->log->getError() << m->message_prefix 3124 *m->log->getError() << m->message_prefix
3110 << ": there are warnings; original file kept in " << backup << "\n"; 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 #include <regex> 3 #include <regex>
4 4
@@ -15,18 +15,14 @@ QPDFJob::Config::checkConfiguration() @@ -15,18 +15,14 @@ QPDFJob::Config::checkConfiguration()
15 QPDFJob::Config* 15 QPDFJob::Config*
16 QPDFJob::Config::inputFile(std::string const& filename) 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 return this; 19 return this;
24 } 20 }
25 21
26 QPDFJob::Config* 22 QPDFJob::Config*
27 QPDFJob::Config::emptyInput() 23 QPDFJob::Config::emptyInput()
28 { 24 {
29 - if (o.m->infilename.empty()) { 25 + if (o.m->infile_name().empty()) {
30 // Various places in QPDFJob.cc used to know that the empty string for infile means empty. 26 // Various places in QPDFJob.cc used to know that the empty string for infile means empty.
31 // This approach meant that passing "" as the argument to inputFile in job JSON, or 27 // This approach meant that passing "" as the argument to inputFile in job JSON, or
32 // equivalently using "" as a positional command-line argument would be the same as 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,7 +148,7 @@ QPDFJob::Config::copyEncryption(std::string const&amp; parameter)
152 if (o.m->deterministic_id) { 148 if (o.m->deterministic_id) {
153 usage("the deterministic-id option is incompatible with encrypted output files"); 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 o.m->copy_encryption = true; 152 o.m->copy_encryption = true;
157 o.m->encrypt = false; 153 o.m->encrypt = false;
158 o.m->decrypt = false; 154 o.m->decrypt = false;
@@ -181,7 +177,7 @@ QPDFJob::Config::deterministicId() @@ -181,7 +177,7 @@ QPDFJob::Config::deterministicId()
181 QPDFJob::Config* 177 QPDFJob::Config*
182 QPDFJob::Config::encryptionFilePassword(std::string const& parameter) 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 return this; 181 return this;
186 } 182 }
187 183
@@ -354,15 +350,15 @@ QPDFJob::Config::testJsonSchema() @@ -354,15 +350,15 @@ QPDFJob::Config::testJsonSchema()
354 QPDFJob::Config* 350 QPDFJob::Config*
355 QPDFJob::Config::keepFilesOpen(std::string const& parameter) 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 return this; 355 return this;
360 } 356 }
361 357
362 QPDFJob::Config* 358 QPDFJob::Config*
363 QPDFJob::Config::keepFilesOpenThreshold(std::string const& parameter) 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 return this; 362 return this;
367 } 363 }
368 364
@@ -978,7 +974,7 @@ QPDFJob::PagesConfig::PagesConfig(Config* c) : @@ -978,7 +974,7 @@ QPDFJob::PagesConfig::PagesConfig(Config* c) :
978 std::shared_ptr<QPDFJob::PagesConfig> 974 std::shared_ptr<QPDFJob::PagesConfig>
979 QPDFJob::Config::pages() 975 QPDFJob::Config::pages()
980 { 976 {
981 - if (!o.m->page_specs.empty()) { 977 + if (!o.m->inputs.selections.empty()) {
982 usage("--pages may only be specified one time"); 978 usage("--pages may only be specified one time");
983 } 979 }
984 return std::shared_ptr<PagesConfig>(new PagesConfig(this)); 980 return std::shared_ptr<PagesConfig>(new PagesConfig(this));
@@ -987,7 +983,7 @@ QPDFJob::Config::pages() @@ -987,7 +983,7 @@ QPDFJob::Config::pages()
987 QPDFJob::Config* 983 QPDFJob::Config*
988 QPDFJob::PagesConfig::endPages() 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 if (n_specs == 0) { 987 if (n_specs == 0) {
992 usage("--pages: no page specifications given"); 988 usage("--pages: no page specifications given");
993 } 989 }
@@ -998,27 +994,25 @@ QPDFJob::PagesConfig* @@ -998,27 +994,25 @@ QPDFJob::PagesConfig*
998 QPDFJob::PagesConfig::pageSpec( 994 QPDFJob::PagesConfig::pageSpec(
999 std::string const& filename, std::string const& range, char const* password) 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 return this; 998 return this;
1003 } 999 }
1004 1000
1005 QPDFJob::PagesConfig* 1001 QPDFJob::PagesConfig*
1006 QPDFJob::PagesConfig::file(std::string const& arg) 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 return this; 1005 return this;
1010 } 1006 }
1011 1007
1012 QPDFJob::PagesConfig* 1008 QPDFJob::PagesConfig*
1013 QPDFJob::PagesConfig::range(std::string const& arg) 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 usage("in --range must follow a file name"); 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 if (!last.range.empty()) { 1015 if (!last.range.empty()) {
1021 - QTC::TC("qpdf", "QPDFJob duplicated range");  
1022 usage("--range already specified for this file"); 1016 usage("--range already specified for this file");
1023 } 1017 }
1024 last.range = arg; 1018 last.range = arg;
@@ -1028,16 +1022,10 @@ QPDFJob::PagesConfig::range(std::string const&amp; arg) @@ -1028,16 +1022,10 @@ QPDFJob::PagesConfig::range(std::string const&amp; arg)
1028 QPDFJob::PagesConfig* 1022 QPDFJob::PagesConfig*
1029 QPDFJob::PagesConfig::password(std::string const& arg) 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 usage("in --pages, --password must follow a file name"); 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 return this; 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 #include <qpdf/JSONHandler.hh> 3 #include <qpdf/JSONHandler.hh>
4 #include <qpdf/QPDFUsage.hh> 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,7 +188,6 @@ QPDF insert foreign page 0
188 QPDFWriter copy use_aes 1 188 QPDFWriter copy use_aes 1
189 QPDFParser indirect without context 0 189 QPDFParser indirect without context 0
190 QPDFObjectHandle trailing data in parse 0 190 QPDFObjectHandle trailing data in parse 0
191 -QPDFJob pages encryption password 0  
192 QPDFTokenizer EOF reading token 0 191 QPDFTokenizer EOF reading token 0
193 QPDFTokenizer EOF reading appendable token 0 192 QPDFTokenizer EOF reading appendable token 0
194 QPDFWriter extra header text no newline 0 193 QPDFWriter extra header text no newline 0
@@ -209,7 +208,6 @@ QPDF not caching overridden objstm object 0 @@ -209,7 +208,6 @@ QPDF not caching overridden objstm object 0
209 QPDF_optimization indirect outlines 0 208 QPDF_optimization indirect outlines 0
210 QPDF xref space 2 209 QPDF xref space 2
211 QPDFJob pages range omitted in middle 0 210 QPDFJob pages range omitted in middle 0
212 -QPDFJob npages 0  
213 QPDFWriter standard deterministic ID 1 211 QPDFWriter standard deterministic ID 1
214 QPDFWriter linearized deterministic ID 1 212 QPDFWriter linearized deterministic ID 1
215 qpdf-c called qpdf_set_deterministic_ID 0 213 qpdf-c called qpdf_set_deterministic_ID 0
@@ -222,7 +220,6 @@ QPDFParser found fake 1 @@ -222,7 +220,6 @@ QPDFParser found fake 1
222 QPDFParser no val for last key 0 220 QPDFParser no val for last key 0
223 QPDF resolve failure to null 0 221 QPDF resolve failure to null 0
224 QPDFObjectHandle errors in parsecontent 0 222 QPDFObjectHandle errors in parsecontent 0
225 -QPDFJob same file error 0  
226 QPDFJob split-pages %d 0 223 QPDFJob split-pages %d 0
227 QPDFJob split-pages .pdf 0 224 QPDFJob split-pages .pdf 0
228 QPDFJob split-pages other 0 225 QPDFJob split-pages other 0
@@ -286,8 +283,6 @@ QPDFAcroFormDocumentHelper non-dictionary field 0 @@ -286,8 +283,6 @@ QPDFAcroFormDocumentHelper non-dictionary field 0
286 QPDFAcroFormDocumentHelper loop 0 283 QPDFAcroFormDocumentHelper loop 0
287 QPDFAcroFormDocumentHelper field found 1 284 QPDFAcroFormDocumentHelper field found 1
288 QPDFAcroFormDocumentHelper annotation found 1 285 QPDFAcroFormDocumentHelper annotation found 1
289 -QPDFJob keep files open n 0  
290 -QPDFJob keep files open y 0  
291 QPDFJob automatically set keep files open 1 286 QPDFJob automatically set keep files open 1
292 QPDFOutlineDocumentHelper string named dest 0 287 QPDFOutlineDocumentHelper string named dest 0
293 QPDFObjectHandle merge top type mismatch 0 288 QPDFObjectHandle merge top type mismatch 0
@@ -338,7 +333,6 @@ QPDFPageDocumentHelper ignore annotation with no appearance 0 @@ -338,7 +333,6 @@ QPDFPageDocumentHelper ignore annotation with no appearance 0
338 QPDFFormFieldObjectHelper replaced BMC at EOF 0 333 QPDFFormFieldObjectHelper replaced BMC at EOF 0
339 QPDFFormFieldObjectHelper fallback Tf 0 334 QPDFFormFieldObjectHelper fallback Tf 0
340 QPDFPageObjectHelper copy shared attribute 1 335 QPDFPageObjectHelper copy shared attribute 1
341 -QPDFJob from_nr from repeat_nr 0  
342 QPDF resolve duplicated page object 0 336 QPDF resolve duplicated page object 0
343 QPDF handle direct page object 0 337 QPDF handle direct page object 0
344 QPDF missing mediabox 0 338 QPDF missing mediabox 0
@@ -453,7 +447,6 @@ QPDFFileSpecObjectHelper empty compat_name 0 @@ -453,7 +447,6 @@ QPDFFileSpecObjectHelper empty compat_name 0
453 QPDFFileSpecObjectHelper non-empty compat_name 0 447 QPDFFileSpecObjectHelper non-empty compat_name 0
454 QPDFAcroFormDocumentHelper copy annotation 3 448 QPDFAcroFormDocumentHelper copy annotation 3
455 QPDFAcroFormDocumentHelper field with parent 3 449 QPDFAcroFormDocumentHelper field with parent 3
456 -QPDFJob pages keeping field from original 0  
457 QPDFObjectHandle merge reuse 0 450 QPDFObjectHandle merge reuse 0
458 QPDFObjectHandle merge generate 0 451 QPDFObjectHandle merge generate 0
459 QPDFAcroFormDocumentHelper replaced DA token 0 452 QPDFAcroFormDocumentHelper replaced DA token 0
@@ -493,8 +486,6 @@ qpdf-c called qpdf_oh_get_binary_string_value 0 @@ -493,8 +486,6 @@ qpdf-c called qpdf_oh_get_binary_string_value 0
493 qpdf-c called qpdf_oh_get_binary_utf8_value 0 486 qpdf-c called qpdf_oh_get_binary_utf8_value 0
494 qpdf-c called qpdf_oh_new_binary_string 0 487 qpdf-c called qpdf_oh_new_binary_string 0
495 qpdf-c called qpdf_oh_new_binary_unicode_string 0 488 qpdf-c called qpdf_oh_new_binary_unicode_string 0
496 -QPDFJob duplicated pages password 0  
497 -QPDFJob misplaced pages password 0  
498 QPDFJob check encrypted encrypted 0 489 QPDFJob check encrypted encrypted 0
499 QPDFJob check encrypted not encrypted 0 490 QPDFJob check encrypted not encrypted 0
500 QPDFJob check password password incorrect 0 491 QPDFJob check password password incorrect 0
@@ -547,8 +538,6 @@ QPDFPageObjectHelper used fallback without copying 0 @@ -547,8 +538,6 @@ QPDFPageObjectHelper used fallback without copying 0
547 QPDF skipping cache for known unchecked object 0 538 QPDF skipping cache for known unchecked object 0
548 QPDF fix dangling triggered xref reconstruction 0 539 QPDF fix dangling triggered xref reconstruction 0
549 QPDF recover xref stream 0 540 QPDF recover xref stream 0
550 -QPDFJob misplaced page range 0  
551 -QPDFJob duplicated range 0  
552 QPDFJob json over/under no file 0 541 QPDFJob json over/under no file 0
553 QPDF_Array copy 1 542 QPDF_Array copy 1
554 QPDF_json stream data not string 0 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 qpdf: empty PDF: checking for shared resources 1 qpdf: empty PDF: checking for shared resources
54 qpdf: no shared resources found 2 qpdf: no shared resources found
  3 +qpdf: selecting --keep-open-files=n
  4 +qpdf: processing 001-kfo.pdf
55 qpdf: 001-kfo.pdf: checking for shared resources 5 qpdf: 001-kfo.pdf: checking for shared resources
56 qpdf: no shared resources found 6 qpdf: no shared resources found
  7 +qpdf: processing 002-kfo.pdf
57 qpdf: 002-kfo.pdf: checking for shared resources 8 qpdf: 002-kfo.pdf: checking for shared resources
58 qpdf: no shared resources found 9 qpdf: no shared resources found
  10 +qpdf: processing 003-kfo.pdf
59 qpdf: 003-kfo.pdf: checking for shared resources 11 qpdf: 003-kfo.pdf: checking for shared resources
60 qpdf: no shared resources found 12 qpdf: no shared resources found
  13 +qpdf: processing 004-kfo.pdf
61 qpdf: 004-kfo.pdf: checking for shared resources 14 qpdf: 004-kfo.pdf: checking for shared resources
62 qpdf: no shared resources found 15 qpdf: no shared resources found
  16 +qpdf: processing 005-kfo.pdf
63 qpdf: 005-kfo.pdf: checking for shared resources 17 qpdf: 005-kfo.pdf: checking for shared resources
64 qpdf: no shared resources found 18 qpdf: no shared resources found
  19 +qpdf: processing 006-kfo.pdf
65 qpdf: 006-kfo.pdf: checking for shared resources 20 qpdf: 006-kfo.pdf: checking for shared resources
66 qpdf: no shared resources found 21 qpdf: no shared resources found
  22 +qpdf: processing 007-kfo.pdf
67 qpdf: 007-kfo.pdf: checking for shared resources 23 qpdf: 007-kfo.pdf: checking for shared resources
68 qpdf: no shared resources found 24 qpdf: no shared resources found
  25 +qpdf: processing 008-kfo.pdf
69 qpdf: 008-kfo.pdf: checking for shared resources 26 qpdf: 008-kfo.pdf: checking for shared resources
70 qpdf: no shared resources found 27 qpdf: no shared resources found
  28 +qpdf: processing 009-kfo.pdf
71 qpdf: 009-kfo.pdf: checking for shared resources 29 qpdf: 009-kfo.pdf: checking for shared resources
72 qpdf: no shared resources found 30 qpdf: no shared resources found
  31 +qpdf: processing 010-kfo.pdf
73 qpdf: 010-kfo.pdf: checking for shared resources 32 qpdf: 010-kfo.pdf: checking for shared resources
74 qpdf: no shared resources found 33 qpdf: no shared resources found
  34 +qpdf: processing 011-kfo.pdf
75 qpdf: 011-kfo.pdf: checking for shared resources 35 qpdf: 011-kfo.pdf: checking for shared resources
76 qpdf: no shared resources found 36 qpdf: no shared resources found
  37 +qpdf: processing 012-kfo.pdf
77 qpdf: 012-kfo.pdf: checking for shared resources 38 qpdf: 012-kfo.pdf: checking for shared resources
78 qpdf: no shared resources found 39 qpdf: no shared resources found
  40 +qpdf: processing 013-kfo.pdf
79 qpdf: 013-kfo.pdf: checking for shared resources 41 qpdf: 013-kfo.pdf: checking for shared resources
80 qpdf: no shared resources found 42 qpdf: no shared resources found
  43 +qpdf: processing 014-kfo.pdf
81 qpdf: 014-kfo.pdf: checking for shared resources 44 qpdf: 014-kfo.pdf: checking for shared resources
82 qpdf: no shared resources found 45 qpdf: no shared resources found
  46 +qpdf: processing 015-kfo.pdf
83 qpdf: 015-kfo.pdf: checking for shared resources 47 qpdf: 015-kfo.pdf: checking for shared resources
84 qpdf: no shared resources found 48 qpdf: no shared resources found
  49 +qpdf: processing 016-kfo.pdf
85 qpdf: 016-kfo.pdf: checking for shared resources 50 qpdf: 016-kfo.pdf: checking for shared resources
86 qpdf: no shared resources found 51 qpdf: no shared resources found
  52 +qpdf: processing 017-kfo.pdf
87 qpdf: 017-kfo.pdf: checking for shared resources 53 qpdf: 017-kfo.pdf: checking for shared resources
88 qpdf: no shared resources found 54 qpdf: no shared resources found
  55 +qpdf: processing 018-kfo.pdf
89 qpdf: 018-kfo.pdf: checking for shared resources 56 qpdf: 018-kfo.pdf: checking for shared resources
90 qpdf: no shared resources found 57 qpdf: no shared resources found
  58 +qpdf: processing 019-kfo.pdf
91 qpdf: 019-kfo.pdf: checking for shared resources 59 qpdf: 019-kfo.pdf: checking for shared resources
92 qpdf: no shared resources found 60 qpdf: no shared resources found
  61 +qpdf: processing 020-kfo.pdf
93 qpdf: 020-kfo.pdf: checking for shared resources 62 qpdf: 020-kfo.pdf: checking for shared resources
94 qpdf: no shared resources found 63 qpdf: no shared resources found
  64 +qpdf: processing 021-kfo.pdf
95 qpdf: 021-kfo.pdf: checking for shared resources 65 qpdf: 021-kfo.pdf: checking for shared resources
96 qpdf: no shared resources found 66 qpdf: no shared resources found
  67 +qpdf: processing 022-kfo.pdf
97 qpdf: 022-kfo.pdf: checking for shared resources 68 qpdf: 022-kfo.pdf: checking for shared resources
98 qpdf: no shared resources found 69 qpdf: no shared resources found
  70 +qpdf: processing 023-kfo.pdf
99 qpdf: 023-kfo.pdf: checking for shared resources 71 qpdf: 023-kfo.pdf: checking for shared resources
100 qpdf: no shared resources found 72 qpdf: no shared resources found
  73 +qpdf: processing 024-kfo.pdf
101 qpdf: 024-kfo.pdf: checking for shared resources 74 qpdf: 024-kfo.pdf: checking for shared resources
102 qpdf: no shared resources found 75 qpdf: no shared resources found
  76 +qpdf: processing 025-kfo.pdf
103 qpdf: 025-kfo.pdf: checking for shared resources 77 qpdf: 025-kfo.pdf: checking for shared resources
104 qpdf: no shared resources found 78 qpdf: no shared resources found
  79 +qpdf: processing 026-kfo.pdf
105 qpdf: 026-kfo.pdf: checking for shared resources 80 qpdf: 026-kfo.pdf: checking for shared resources
106 qpdf: no shared resources found 81 qpdf: no shared resources found
  82 +qpdf: processing 027-kfo.pdf
107 qpdf: 027-kfo.pdf: checking for shared resources 83 qpdf: 027-kfo.pdf: checking for shared resources
108 qpdf: no shared resources found 84 qpdf: no shared resources found
  85 +qpdf: processing 028-kfo.pdf
109 qpdf: 028-kfo.pdf: checking for shared resources 86 qpdf: 028-kfo.pdf: checking for shared resources
110 qpdf: no shared resources found 87 qpdf: no shared resources found
  88 +qpdf: processing 029-kfo.pdf
111 qpdf: 029-kfo.pdf: checking for shared resources 89 qpdf: 029-kfo.pdf: checking for shared resources
112 qpdf: no shared resources found 90 qpdf: no shared resources found
  91 +qpdf: processing 030-kfo.pdf
113 qpdf: 030-kfo.pdf: checking for shared resources 92 qpdf: 030-kfo.pdf: checking for shared resources
114 qpdf: no shared resources found 93 qpdf: no shared resources found
  94 +qpdf: processing 031-kfo.pdf
115 qpdf: 031-kfo.pdf: checking for shared resources 95 qpdf: 031-kfo.pdf: checking for shared resources
116 qpdf: no shared resources found 96 qpdf: no shared resources found
  97 +qpdf: processing 032-kfo.pdf
117 qpdf: 032-kfo.pdf: checking for shared resources 98 qpdf: 032-kfo.pdf: checking for shared resources
118 qpdf: no shared resources found 99 qpdf: no shared resources found
  100 +qpdf: processing 033-kfo.pdf
119 qpdf: 033-kfo.pdf: checking for shared resources 101 qpdf: 033-kfo.pdf: checking for shared resources
120 qpdf: no shared resources found 102 qpdf: no shared resources found
  103 +qpdf: processing 034-kfo.pdf
121 qpdf: 034-kfo.pdf: checking for shared resources 104 qpdf: 034-kfo.pdf: checking for shared resources
122 qpdf: no shared resources found 105 qpdf: no shared resources found
  106 +qpdf: processing 035-kfo.pdf
123 qpdf: 035-kfo.pdf: checking for shared resources 107 qpdf: 035-kfo.pdf: checking for shared resources
124 qpdf: no shared resources found 108 qpdf: no shared resources found
  109 +qpdf: processing 036-kfo.pdf
125 qpdf: 036-kfo.pdf: checking for shared resources 110 qpdf: 036-kfo.pdf: checking for shared resources
126 qpdf: no shared resources found 111 qpdf: no shared resources found
  112 +qpdf: processing 037-kfo.pdf
127 qpdf: 037-kfo.pdf: checking for shared resources 113 qpdf: 037-kfo.pdf: checking for shared resources
128 qpdf: no shared resources found 114 qpdf: no shared resources found
  115 +qpdf: processing 038-kfo.pdf
129 qpdf: 038-kfo.pdf: checking for shared resources 116 qpdf: 038-kfo.pdf: checking for shared resources
130 qpdf: no shared resources found 117 qpdf: no shared resources found
  118 +qpdf: processing 039-kfo.pdf
131 qpdf: 039-kfo.pdf: checking for shared resources 119 qpdf: 039-kfo.pdf: checking for shared resources
132 qpdf: no shared resources found 120 qpdf: no shared resources found
  121 +qpdf: processing 040-kfo.pdf
133 qpdf: 040-kfo.pdf: checking for shared resources 122 qpdf: 040-kfo.pdf: checking for shared resources
134 qpdf: no shared resources found 123 qpdf: no shared resources found
  124 +qpdf: processing 041-kfo.pdf
135 qpdf: 041-kfo.pdf: checking for shared resources 125 qpdf: 041-kfo.pdf: checking for shared resources
136 qpdf: no shared resources found 126 qpdf: no shared resources found
  127 +qpdf: processing 042-kfo.pdf
137 qpdf: 042-kfo.pdf: checking for shared resources 128 qpdf: 042-kfo.pdf: checking for shared resources
138 qpdf: no shared resources found 129 qpdf: no shared resources found
  130 +qpdf: processing 043-kfo.pdf
139 qpdf: 043-kfo.pdf: checking for shared resources 131 qpdf: 043-kfo.pdf: checking for shared resources
140 qpdf: no shared resources found 132 qpdf: no shared resources found
  133 +qpdf: processing 044-kfo.pdf
141 qpdf: 044-kfo.pdf: checking for shared resources 134 qpdf: 044-kfo.pdf: checking for shared resources
142 qpdf: no shared resources found 135 qpdf: no shared resources found
  136 +qpdf: processing 045-kfo.pdf
143 qpdf: 045-kfo.pdf: checking for shared resources 137 qpdf: 045-kfo.pdf: checking for shared resources
144 qpdf: no shared resources found 138 qpdf: no shared resources found
  139 +qpdf: processing 046-kfo.pdf
145 qpdf: 046-kfo.pdf: checking for shared resources 140 qpdf: 046-kfo.pdf: checking for shared resources
146 qpdf: no shared resources found 141 qpdf: no shared resources found
  142 +qpdf: processing 047-kfo.pdf
147 qpdf: 047-kfo.pdf: checking for shared resources 143 qpdf: 047-kfo.pdf: checking for shared resources
148 qpdf: no shared resources found 144 qpdf: no shared resources found
  145 +qpdf: processing 048-kfo.pdf
149 qpdf: 048-kfo.pdf: checking for shared resources 146 qpdf: 048-kfo.pdf: checking for shared resources
150 qpdf: no shared resources found 147 qpdf: no shared resources found
  148 +qpdf: processing 049-kfo.pdf
151 qpdf: 049-kfo.pdf: checking for shared resources 149 qpdf: 049-kfo.pdf: checking for shared resources
152 qpdf: no shared resources found 150 qpdf: no shared resources found
  151 +qpdf: processing 050-kfo.pdf
153 qpdf: 050-kfo.pdf: checking for shared resources 152 qpdf: 050-kfo.pdf: checking for shared resources
154 qpdf: no shared resources found 153 qpdf: no shared resources found
  154 +qpdf: processing 051-kfo.pdf
155 qpdf: 051-kfo.pdf: checking for shared resources 155 qpdf: 051-kfo.pdf: checking for shared resources
156 qpdf: no shared resources found 156 qpdf: no shared resources found
157 qpdf: removing unreferenced pages from primary input 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 qpdf: empty PDF: checking for shared resources 1 qpdf: empty PDF: checking for shared resources
13 qpdf: no shared resources found 2 qpdf: no shared resources found
  3 +qpdf: selecting --keep-open-files=y
  4 +qpdf: processing 010-kfo.pdf
14 qpdf: 010-kfo.pdf: checking for shared resources 5 qpdf: 010-kfo.pdf: checking for shared resources
15 qpdf: no shared resources found 6 qpdf: no shared resources found
  7 +qpdf: processing 011-kfo.pdf
16 qpdf: 011-kfo.pdf: checking for shared resources 8 qpdf: 011-kfo.pdf: checking for shared resources
17 qpdf: no shared resources found 9 qpdf: no shared resources found
  10 +qpdf: processing 012-kfo.pdf
18 qpdf: 012-kfo.pdf: checking for shared resources 11 qpdf: 012-kfo.pdf: checking for shared resources
19 qpdf: no shared resources found 12 qpdf: no shared resources found
  13 +qpdf: processing 013-kfo.pdf
20 qpdf: 013-kfo.pdf: checking for shared resources 14 qpdf: 013-kfo.pdf: checking for shared resources
21 qpdf: no shared resources found 15 qpdf: no shared resources found
  16 +qpdf: processing 014-kfo.pdf
22 qpdf: 014-kfo.pdf: checking for shared resources 17 qpdf: 014-kfo.pdf: checking for shared resources
23 qpdf: no shared resources found 18 qpdf: no shared resources found
  19 +qpdf: processing 015-kfo.pdf
24 qpdf: 015-kfo.pdf: checking for shared resources 20 qpdf: 015-kfo.pdf: checking for shared resources
25 qpdf: no shared resources found 21 qpdf: no shared resources found
  22 +qpdf: processing 016-kfo.pdf
26 qpdf: 016-kfo.pdf: checking for shared resources 23 qpdf: 016-kfo.pdf: checking for shared resources
27 qpdf: no shared resources found 24 qpdf: no shared resources found
  25 +qpdf: processing 017-kfo.pdf
28 qpdf: 017-kfo.pdf: checking for shared resources 26 qpdf: 017-kfo.pdf: checking for shared resources
29 qpdf: no shared resources found 27 qpdf: no shared resources found
  28 +qpdf: processing 018-kfo.pdf
30 qpdf: 018-kfo.pdf: checking for shared resources 29 qpdf: 018-kfo.pdf: checking for shared resources
31 qpdf: no shared resources found 30 qpdf: no shared resources found
  31 +qpdf: processing 019-kfo.pdf
32 qpdf: 019-kfo.pdf: checking for shared resources 32 qpdf: 019-kfo.pdf: checking for shared resources
33 qpdf: no shared resources found 33 qpdf: no shared resources found
34 qpdf: removing unreferenced pages from primary input 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 qpdf: empty PDF: checking for shared resources 1 qpdf: empty PDF: checking for shared resources
11 qpdf: no shared resources found 2 qpdf: no shared resources found
  3 +qpdf: processing 001-kfo.pdf
12 qpdf: 001-kfo.pdf: checking for shared resources 4 qpdf: 001-kfo.pdf: checking for shared resources
13 qpdf: no shared resources found 5 qpdf: no shared resources found
  6 +qpdf: processing 002-kfo.pdf
14 qpdf: 002-kfo.pdf: checking for shared resources 7 qpdf: 002-kfo.pdf: checking for shared resources
15 qpdf: no shared resources found 8 qpdf: no shared resources found
  9 +qpdf: processing 003-kfo.pdf
16 qpdf: 003-kfo.pdf: checking for shared resources 10 qpdf: 003-kfo.pdf: checking for shared resources
17 qpdf: no shared resources found 11 qpdf: no shared resources found
  12 +qpdf: processing 004-kfo.pdf
18 qpdf: 004-kfo.pdf: checking for shared resources 13 qpdf: 004-kfo.pdf: checking for shared resources
19 qpdf: no shared resources found 14 qpdf: no shared resources found
  15 +qpdf: processing 005-kfo.pdf
20 qpdf: 005-kfo.pdf: checking for shared resources 16 qpdf: 005-kfo.pdf: checking for shared resources
21 qpdf: no shared resources found 17 qpdf: no shared resources found
  18 +qpdf: processing 006-kfo.pdf
22 qpdf: 006-kfo.pdf: checking for shared resources 19 qpdf: 006-kfo.pdf: checking for shared resources
23 qpdf: no shared resources found 20 qpdf: no shared resources found
  21 +qpdf: processing 007-kfo.pdf
24 qpdf: 007-kfo.pdf: checking for shared resources 22 qpdf: 007-kfo.pdf: checking for shared resources
25 qpdf: no shared resources found 23 qpdf: no shared resources found
  24 +qpdf: processing 008-kfo.pdf
26 qpdf: 008-kfo.pdf: checking for shared resources 25 qpdf: 008-kfo.pdf: checking for shared resources
27 qpdf: no shared resources found 26 qpdf: no shared resources found
  27 +qpdf: processing 009-kfo.pdf
28 qpdf: 009-kfo.pdf: checking for shared resources 28 qpdf: 009-kfo.pdf: checking for shared resources
29 qpdf: no shared resources found 29 qpdf: no shared resources found
30 qpdf: removing unreferenced pages from primary input 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 qpdf: empty PDF: checking for shared resources 1 qpdf: empty PDF: checking for shared resources
11 qpdf: no shared resources found 2 qpdf: no shared resources found
  3 +qpdf: processing 001-kfo.pdf
12 qpdf: 001-kfo.pdf: checking for shared resources 4 qpdf: 001-kfo.pdf: checking for shared resources
13 qpdf: no shared resources found 5 qpdf: no shared resources found
  6 +qpdf: processing 002-kfo.pdf
14 qpdf: 002-kfo.pdf: checking for shared resources 7 qpdf: 002-kfo.pdf: checking for shared resources
15 qpdf: no shared resources found 8 qpdf: no shared resources found
  9 +qpdf: processing 003-kfo.pdf
16 qpdf: 003-kfo.pdf: checking for shared resources 10 qpdf: 003-kfo.pdf: checking for shared resources
17 qpdf: no shared resources found 11 qpdf: no shared resources found
  12 +qpdf: processing 004-kfo.pdf
18 qpdf: 004-kfo.pdf: checking for shared resources 13 qpdf: 004-kfo.pdf: checking for shared resources
19 qpdf: no shared resources found 14 qpdf: no shared resources found
  15 +qpdf: processing 005-kfo.pdf
20 qpdf: 005-kfo.pdf: checking for shared resources 16 qpdf: 005-kfo.pdf: checking for shared resources
21 qpdf: no shared resources found 17 qpdf: no shared resources found
  18 +qpdf: processing 006-kfo.pdf
22 qpdf: 006-kfo.pdf: checking for shared resources 19 qpdf: 006-kfo.pdf: checking for shared resources
23 qpdf: no shared resources found 20 qpdf: no shared resources found
  21 +qpdf: processing 007-kfo.pdf
24 qpdf: 007-kfo.pdf: checking for shared resources 22 qpdf: 007-kfo.pdf: checking for shared resources
25 qpdf: no shared resources found 23 qpdf: no shared resources found
  24 +qpdf: processing 008-kfo.pdf
26 qpdf: 008-kfo.pdf: checking for shared resources 25 qpdf: 008-kfo.pdf: checking for shared resources
27 qpdf: no shared resources found 26 qpdf: no shared resources found
  27 +qpdf: processing 009-kfo.pdf
28 qpdf: 009-kfo.pdf: checking for shared resources 28 qpdf: 009-kfo.pdf: checking for shared resources
29 qpdf: no shared resources found 29 qpdf: no shared resources found
30 qpdf: removing unreferenced pages from primary input 30 qpdf: removing unreferenced pages from primary input
qpdf/qtest/qpdf/uo-6.out
1 -qpdf: selecting --keep-open-files=y  
2 qpdf: fxo-red.pdf: checking for shared resources 1 qpdf: fxo-red.pdf: checking for shared resources
3 qpdf: no shared resources found 2 qpdf: no shared resources found
  3 +qpdf: selecting --keep-open-files=y
4 qpdf: removing unreferenced pages from primary input 4 qpdf: removing unreferenced pages from primary input
5 qpdf: adding pages from fxo-red.pdf 5 qpdf: adding pages from fxo-red.pdf
6 qpdf: processing underlay/overlay 6 qpdf: processing underlay/overlay
qpdf/qtest/qpdf/uo-8.out
1 -qpdf: selecting --keep-open-files=y  
2 qpdf: fxo-red.pdf: checking for shared resources 1 qpdf: fxo-red.pdf: checking for shared resources
3 qpdf: no shared resources found 2 qpdf: no shared resources found
  3 +qpdf: selecting --keep-open-files=y
4 qpdf: removing unreferenced pages from primary input 4 qpdf: removing unreferenced pages from primary input
5 qpdf: adding pages from fxo-red.pdf 5 qpdf: adding pages from fxo-red.pdf
6 qpdf: processing underlay/overlay 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 qpdf: selecting --keep-open-files=y 3 qpdf: selecting --keep-open-files=y
2 -qpdf: processing 20-pages.pdf  
3 qpdf: processing ./20-pages.pdf 4 qpdf: processing ./20-pages.pdf
4 -qpdf: processing minimal.pdf  
5 qpdf: ./20-pages.pdf: checking for shared resources 5 qpdf: ./20-pages.pdf: checking for shared resources
6 qpdf: no shared resources found 6 qpdf: no shared resources found
  7 +qpdf: processing 20-pages.pdf
7 qpdf: 20-pages.pdf: checking for shared resources 8 qpdf: 20-pages.pdf: checking for shared resources
8 qpdf: no shared resources found 9 qpdf: no shared resources found
  10 +qpdf: processing minimal.pdf
9 qpdf: minimal.pdf: checking for shared resources 11 qpdf: minimal.pdf: checking for shared resources
10 qpdf: no shared resources found 12 qpdf: no shared resources found
11 -qpdf: page-labels-and-outlines.pdf: checking for shared resources  
12 -qpdf: no shared resources found  
13 qpdf: removing unreferenced pages from primary input 13 qpdf: removing unreferenced pages from primary input
14 qpdf: adding pages from page-labels-and-outlines.pdf 14 qpdf: adding pages from page-labels-and-outlines.pdf
15 qpdf: adding pages from 20-pages.pdf 15 qpdf: adding pages from 20-pages.pdf