diff --git a/include/qpdf/QPDFJob.hh b/include/qpdf/QPDFJob.hh index f6719a9..62992cd 100644 --- a/include/qpdf/QPDFJob.hh +++ b/include/qpdf/QPDFJob.hh @@ -156,16 +156,6 @@ class QPDFJob bool replace{false}; }; - struct PageSpec - { - PageSpec( - std::string const& filename, std::string const& password, std::string const& range); - - std::string filename; - std::string password; - std::string range; - }; - public: // CONFIGURATION @@ -425,58 +415,16 @@ class QPDFJob [[deprecated("use job_json_schema(version)")]] static std::string QPDF_DLL job_json_schema_v1(); private: - struct RotationSpec - { - RotationSpec(int angle = 0, bool relative = false) : - angle(angle), - relative(relative) - { - } - - int angle; - bool relative; - }; + struct PageNo; + struct Selection; + struct Input; + struct Inputs; + struct RotationSpec; + struct UnderOverlay; + struct PageLabelSpec; enum password_mode_e { pm_bytes, pm_hex_bytes, pm_unicode, pm_auto }; - struct UnderOverlay - { - UnderOverlay(char const* which) : - which(which), - to_nr("1-z"), - from_nr("1-z"), - repeat_nr("") - { - } - - std::string which; - std::string filename; - std::string password; - std::string to_nr; - std::string from_nr; - std::string repeat_nr; - std::unique_ptr pdf; - std::vector to_pagenos; - std::vector from_pagenos; - std::vector repeat_pagenos; - }; - - struct PageLabelSpec - { - PageLabelSpec( - int first_page, qpdf_page_label_e label_type, int start_num, std::string_view prefix) : - first_page(first_page), - label_type(label_type), - start_num(start_num), - prefix(prefix) - { - } - int first_page; - qpdf_page_label_e label_type; - int start_num{1}; - std::string prefix; - }; - // Helper functions static void usage(std::string const& msg); static JSON json_schema(int json_version, std::set* keys = nullptr); @@ -513,20 +461,19 @@ class QPDFJob // Transformations void setQPDFOptions(QPDF& pdf); - void handlePageSpecs(QPDF& pdf, std::vector>& page_heap); + void handlePageSpecs(QPDF& pdf); bool shouldRemoveUnreferencedResources(QPDF& pdf); void handleRotations(QPDF& pdf); void getUOPagenos( - std::vector& uo, std::map>>& pagenos); + std::vector& uo, std::vector>>& pagenos); void handleUnderOverlay(QPDF& pdf); std::string doUnderOverlayForPage( QPDF& pdf, UnderOverlay& uo, - std::map>>& pagenos, - size_t page_idx, + std::vector>>& pagenos, + PageNo const& page_idx, size_t uo_idx, std::map>& fo, - std::vector& pages, QPDFPageObjectHelper& dest_page); void validateUnderOverlay(QPDF& pdf, UnderOverlay* uo); void handleTransformations(QPDF& pdf); @@ -568,159 +515,8 @@ class QPDFJob enum remove_unref_e { re_auto, re_yes, re_no }; - class Members - { - friend class QPDFJob; - - public: - QPDF_DLL - ~Members() = default; + class Members; - private: - // These default values are duplicated in help and docs. - static int constexpr DEFAULT_KEEP_FILES_OPEN_THRESHOLD = 200; - static int constexpr DEFAULT_OI_MIN_WIDTH = 128; - static int constexpr DEFAULT_OI_MIN_HEIGHT = 128; - static int constexpr DEFAULT_OI_MIN_AREA = 16384; - static int constexpr DEFAULT_II_MIN_BYTES = 1024; - - Members(); - Members(Members const&) = delete; - - std::shared_ptr log; - std::string message_prefix{"qpdf"}; - bool warnings{false}; - unsigned long encryption_status{0}; - bool verbose{false}; - std::string password; - bool linearize{false}; - bool decrypt{false}; - bool remove_restrictions{false}; - int split_pages{0}; - bool progress{false}; - std::function progress_handler{nullptr}; - bool suppress_warnings{false}; - bool warnings_exit_zero{false}; - bool copy_encryption{false}; - std::string encryption_file; - std::string encryption_file_password; - bool encrypt{false}; - bool password_is_hex_key{false}; - bool suppress_password_recovery{false}; - password_mode_e password_mode{pm_auto}; - bool allow_insecure{false}; - bool allow_weak_crypto{false}; - std::string user_password; - std::string owner_password; - int keylen{0}; - bool r2_print{true}; - bool r2_modify{true}; - bool r2_extract{true}; - bool r2_annotate{true}; - bool r3_accessibility{true}; - bool r3_extract{true}; - bool r3_assemble{true}; - bool r3_annotate_and_form{true}; - bool r3_form_filling{true}; - bool r3_modify_other{true}; - qpdf_r3_print_e r3_print{qpdf_r3p_full}; - bool force_V4{false}; - bool force_R5{false}; - bool cleartext_metadata{false}; - bool use_aes{false}; - bool stream_data_set{false}; - qpdf_stream_data_e stream_data_mode{qpdf_s_compress}; - bool compress_streams{true}; - bool compress_streams_set{false}; - bool recompress_flate{false}; - bool recompress_flate_set{false}; - int compression_level{-1}; - int jpeg_quality{-1}; - qpdf_stream_decode_level_e decode_level{qpdf_dl_generalized}; - bool decode_level_set{false}; - bool normalize_set{false}; - bool normalize{false}; - bool suppress_recovery{false}; - bool object_stream_set{false}; - qpdf_object_stream_e object_stream_mode{qpdf_o_preserve}; - bool ignore_xref_streams{false}; - bool qdf_mode{false}; - bool preserve_unreferenced_objects{false}; - remove_unref_e remove_unreferenced_page_resources{re_auto}; - bool keep_files_open{true}; - bool keep_files_open_set{false}; - size_t keep_files_open_threshold{DEFAULT_KEEP_FILES_OPEN_THRESHOLD}; - bool newline_before_endstream{false}; - std::string linearize_pass1; - bool coalesce_contents{false}; - bool flatten_annotations{false}; - int flatten_annotations_required{0}; - int flatten_annotations_forbidden{an_invisible | an_hidden}; - bool generate_appearances{false}; - PDFVersion max_input_version; - std::string min_version; - std::string force_version; - bool show_npages{false}; - bool deterministic_id{false}; - bool static_id{false}; - bool static_aes_iv{false}; - bool suppress_original_object_id{false}; - bool show_encryption{false}; - bool show_encryption_key{false}; - bool check_linearization{false}; - bool show_linearization{false}; - bool show_xref{false}; - bool show_trailer{false}; - int show_obj{0}; - int show_gen{0}; - bool show_raw_stream_data{false}; - bool show_filtered_stream_data{false}; - bool show_pages{false}; - bool show_page_images{false}; - std::vector collate; - bool flatten_rotation{false}; - bool list_attachments{false}; - std::string attachment_to_show; - std::list attachments_to_remove; - std::list attachments_to_add; - std::list attachments_to_copy; - int json_version{0}; - std::set json_keys; - std::set json_objects; - qpdf_json_stream_data_e json_stream_data{qpdf_sj_none}; - bool json_stream_data_set{false}; - std::string json_stream_prefix; - bool test_json_schema{false}; - bool check{false}; - bool optimize_images{false}; - bool externalize_inline_images{false}; - bool keep_inline_images{false}; - bool remove_info{false}; - bool remove_metadata{false}; - bool remove_page_labels{false}; - bool remove_structure{false}; - size_t oi_min_width{DEFAULT_OI_MIN_WIDTH}; - size_t oi_min_height{DEFAULT_OI_MIN_HEIGHT}; - size_t oi_min_area{DEFAULT_OI_MIN_AREA}; - size_t ii_min_bytes{DEFAULT_II_MIN_BYTES}; - std::vector underlay; - std::vector overlay; - UnderOverlay* under_overlay{nullptr}; - std::vector page_specs; - std::map rotations; - bool require_outfile{true}; - bool replace_input{false}; - bool check_is_encrypted{false}; - bool check_requires_password{false}; - std::string infilename; - bool empty_input{false}; - std::string outfilename; - bool json_input{false}; - bool json_output{false}; - std::string update_from_json; - bool report_mem_usage{false}; - std::vector page_label_specs; - }; std::shared_ptr m; }; diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index 10d8b5f..1242d63 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -58,18 +58,7 @@ namespace std::shared_ptr config; }; - struct QPDFPageData - { - QPDFPageData(std::string const& filename, QPDF* qpdf, std::string const& range); - QPDFPageData(QPDFPageData const& other, int page); - - std::string filename; - QPDF* qpdf; - std::vector orig_pages; - std::vector selected_pages; - }; - - class ProgressReporter: public QPDFWriter::ProgressReporter + class ProgressReporter final: public QPDFWriter::ProgressReporter { public: ProgressReporter(Pipeline& p, std::string const& prefix, char const* filename) : @@ -78,8 +67,12 @@ namespace filename(filename) { } - ~ProgressReporter() override = default; - void reportProgress(int) override; + ~ProgressReporter() final = default; + void + reportProgress(int percentage) final + { + p << prefix << ": " << filename << ": write progress: " << percentage << "%\n"; + } private: Pipeline& p; @@ -239,47 +232,32 @@ ImageOptimizer::provideStreamData(QPDFObjGen const&, Pipeline* pipeline) image.pipeStreamData(p.get(), 0, decode_level, false, false); } -QPDFJob::PageSpec::PageSpec( - std::string const& filename, std::string const& password, std::string const& range) : - filename(filename), - password(password.empty() ? "" : password), - range(range) +// Page number (1 based) and index (0 based). Defaults to page number 1 / index 0. +struct QPDFJob::PageNo { -} + PageNo() = default; + PageNo(PageNo const&) = default; -QPDFPageData::QPDFPageData(std::string const& filename, QPDF* qpdf, std::string const& range) : - filename(filename), - qpdf(qpdf), - orig_pages(qpdf->getAllPages()) -{ - try { - selected_pages = QUtil::parse_numrange(range.c_str(), QIntC::to_int(orig_pages.size())); - } catch (std::runtime_error& e) { - throw std::runtime_error("parsing numeric range for " + filename + ": " + e.what()); + PageNo(int no) : + idx{QIntC::to_size(no - 1)}, + no{no} + { } -} -QPDFPageData::QPDFPageData(QPDFPageData const& other, int page) : - filename(other.filename), - qpdf(other.qpdf), - orig_pages(other.orig_pages) -{ - selected_pages.push_back(page); -} - -void -ProgressReporter::reportProgress(int percentage) -{ - p << prefix << ": " << filename << ": write progress: " << percentage << "%\n"; -} + PageNo& + operator++() + { + ++idx; + ++no; + return *this; + } -QPDFJob::Members::Members() : - log(QPDFLogger::defaultLogger()) -{ -} + size_t idx{0}; + int no{1}; +}; QPDFJob::QPDFJob() : - m(new Members()) + m(std::make_shared(*this)) { } @@ -416,7 +394,7 @@ QPDFJob::createQPDF() checkConfiguration(); std::unique_ptr pdf_sp; try { - processFile(pdf_sp, m->infilename.data(), m->password.data(), true, true); + processFile(pdf_sp, m->infile_nm(), m->password.data(), true, true); } catch (QPDFExc& e) { if (e.getErrorCode() == qpdf_e_password) { // Allow certain operations to work when an incorrect password is supplied. @@ -447,40 +425,34 @@ QPDFJob::createQPDF() pdf.updateFromJSON(m->update_from_json); } - std::vector> page_heap; - if (!m->page_specs.empty()) { - handlePageSpecs(pdf, page_heap); - } + handlePageSpecs(pdf); if (!m->rotations.empty()) { handleRotations(pdf); } handleUnderOverlay(pdf); handleTransformations(pdf); + m->warnings |= m->inputs.clear(); + + auto root = pdf.getRoot(); if (m->remove_info) { auto trailer = pdf.getTrailer(); - auto mod_date = trailer.getKey("/Info").getKeyIfDict("/ModDate"); + auto mod_date = trailer["/Info"]["/ModDate"]; if (mod_date.null()) { - trailer.removeKey("/Info"); + trailer.erase("/Info"); } else { - auto info = trailer.replaceKeyAndGetNew( - "/Info", pdf.makeIndirectObject(QPDFObjectHandle::newDictionary())); - info.replaceKey("/ModDate", mod_date); + trailer.replaceKey( + "/Info", pdf.makeIndirectObject(Dictionary({{"/ModDate", mod_date}}))); } - pdf.getRoot().removeKey("/Metadata"); + root.erase("/Metadata"); } if (m->remove_metadata) { - pdf.getRoot().removeKey("/Metadata"); + root.erase("/Metadata"); } if (m->remove_structure) { - pdf.getRoot().removeKey("/StructTreeRoot"); - pdf.getRoot().removeKey("/MarkInfo"); + root.erase("/StructTreeRoot"); + root.erase("/MarkInfo"); } - for (auto& foreign: page_heap) { - if (foreign->anyWarnings()) { - m->warnings = true; - } - } return pdf_sp; } @@ -595,10 +567,10 @@ QPDFJob::checkConfiguration() // standard output. m->outfilename = "-"; } - if (m->infilename.empty() && !m->empty_input) { + if (m->infile_name().empty() && !m->empty_input) { usage("an input file name is required"); } - if (m->replace_input && m->infilename.empty()) { + if (m->replace_input && m->infile_name().empty()) { usage("--replace-input may not be used with --empty"); } if (m->require_outfile && m->outfilename.empty() && !m->replace_input) { @@ -637,8 +609,7 @@ QPDFJob::checkConfiguration() if (save_to_stdout) { m->log->saveToStandardOutput(true); } - if (!m->split_pages && QUtil::same_file(m->infilename.data(), m->outfilename.data())) { - QTC::TC("qpdf", "QPDFJob same file error"); + if (!m->split_pages && QUtil::same_file(m->infile_nm(), m->outfilename.data())) { usage( "input file and output file are the same; use --replace-input to intentionally " "overwrite the input file"); @@ -762,7 +733,7 @@ QPDFJob::doCheck(QPDF& pdf) // may continue to perform additional checks after finding errors. bool okay = true; auto& cout = *m->log->getInfo(); - cout << "checking " << m->infilename << "\n"; + cout << "checking " << m->infile_name() << "\n"; QPDF::JobSetter::setCheckMode(pdf, true); try { int extension_level = pdf.getExtensionLevel(); @@ -933,7 +904,7 @@ QPDFJob::doListAttachments(QPDF& pdf) }); } } else { - *m->log->getInfo() << m->infilename << " has no embedded files\n"; + *m->log->getInfo() << m->infile_name() << " has no embedded files\n"; } } @@ -1699,7 +1670,6 @@ QPDFJob::doInspection(QPDF& pdf) doCheck(pdf); } if (m->show_npages) { - QTC::TC("qpdf", "QPDFJob npages"); cout << pdf.getRoot().getKey("/Pages").getKey("/Count").getIntValue() << "\n"; } if (m->show_encryption) { @@ -1707,9 +1677,9 @@ QPDFJob::doInspection(QPDF& pdf) } if (m->check_linearization) { if (!pdf.isLinearized()) { - cout << m->infilename << " is not linearized\n"; + cout << m->infile_name() << " is not linearized\n"; } else if (pdf.checkLinearization()) { - cout << m->infilename << ": no linearization errors\n"; + cout << m->infile_name() << ": no linearization errors\n"; } else { m->warnings = true; } @@ -1718,7 +1688,7 @@ QPDFJob::doInspection(QPDF& pdf) if (pdf.isLinearized()) { pdf.showLinearizationData(); } else { - cout << m->infilename << " is not linearized\n"; + cout << m->infile_name() << " is not linearized\n"; } } if (m->show_xref) { @@ -1755,7 +1725,7 @@ QPDFJob::doProcessOnce( if (empty) { pdf->emptyPDF(); } else if (main_input && m->json_input) { - pdf->createFromJSON(m->infilename); + pdf->createFromJSON(m->infile_name()); } else { fn(pdf.get(), password); } @@ -1867,25 +1837,22 @@ QPDFJob::processInputSource( void QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) { - auto& main_pdh = pdf.pages(); - int main_npages = QIntC::to_int(main_pdh.getAllPages().size()); processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false); - QPDFPageDocumentHelper uo_pdh(*(uo->pdf)); - int uo_npages = QIntC::to_int(uo_pdh.getAllPages().size()); try { - uo->to_pagenos = QUtil::parse_numrange(uo->to_nr.c_str(), main_npages); + uo->to_pagenos = + QUtil::parse_numrange(uo->to_nr.data(), static_cast(pdf.getAllPages().size())); } catch (std::runtime_error& e) { throw std::runtime_error( "parsing numeric range for " + uo->which + " \"to\" pages: " + e.what()); } try { if (uo->from_nr.empty()) { - QTC::TC("qpdf", "QPDFJob from_nr from repeat_nr"); uo->from_nr = uo->repeat_nr; } - uo->from_pagenos = QUtil::parse_numrange(uo->from_nr.c_str(), uo_npages); + int uo_npages = static_cast(uo->pdf->getAllPages().size()); + uo->from_pagenos = QUtil::parse_numrange(uo->from_nr.data(), uo_npages); if (!uo->repeat_nr.empty()) { - uo->repeat_pagenos = QUtil::parse_numrange(uo->repeat_nr.c_str(), uo_npages); + uo->repeat_pagenos = QUtil::parse_numrange(uo->repeat_nr.data(), uo_npages); } } catch (std::runtime_error& e) { throw std::runtime_error( @@ -1897,29 +1864,28 @@ std::string QPDFJob::doUnderOverlayForPage( QPDF& pdf, UnderOverlay& uo, - std::map>>& pagenos, - size_t page_idx, + std::vector>>& pagenos, + PageNo const& pageno, size_t uo_idx, std::map>& fo, - std::vector& pages, QPDFPageObjectHelper& dest_page) { - int pageno = 1 + QIntC::to_int(page_idx); - if (!(pagenos.contains(pageno) && pagenos[pageno].contains(uo_idx))) { + if (!(uo.pdf && pagenos[pageno.idx].contains(uo_idx))) { return ""; } auto& dest_afdh = dest_page.qpdf()->acroform(); + auto const& pages = uo.pdf->getAllPages(); std::string content; int min_suffix = 1; QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true); - for (int from_pageno: pagenos[pageno][uo_idx]) { + for (PageNo from_no: pagenos[pageno.idx][uo_idx]) { doIfVerbose([&](Pipeline& v, std::string const& prefix) { - v << " " << uo.filename << " " << uo.which << " " << from_pageno << "\n"; + v << " " << uo.filename << " " << uo.which << " " << from_no.no << "\n"; }); - auto from_page = pages.at(QIntC::to_size(from_pageno - 1)); - if (!fo[from_pageno].contains(uo_idx)) { - fo[from_pageno][uo_idx] = pdf.copyForeignObject(from_page.getFormXObjectForPage()); + QPDFPageObjectHelper from_page = pages.at(from_no.idx); + if (!fo[from_no.no].contains(uo_idx)) { + fo[from_no.no][uo_idx] = pdf.copyForeignObject(from_page.getFormXObjectForPage()); } // If the same page is overlaid or underlaid multiple times, we'll generate multiple names @@ -1927,13 +1893,13 @@ QPDFJob::doUnderOverlayForPage( std::string name = resources.getUniqueResourceName("/Fx", min_suffix); QPDFMatrix cm; std::string new_content = dest_page.placeFormXObject( - fo[from_pageno][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); + fo[from_no.no][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); dest_page.copyAnnotations(from_page, cm, &dest_afdh, &from_page.qpdf()->acroform()); if (!new_content.empty()) { resources.mergeResources("<< /XObject << >> >>"_qpdf); auto xobject = resources.getKey("/XObject"); if (xobject.isDictionary()) { - xobject.replaceKey(name, fo[from_pageno][uo_idx]); + xobject.replaceKey(name, fo[from_no.no][uo_idx]); } ++min_suffix; content += new_content; @@ -1945,14 +1911,15 @@ QPDFJob::doUnderOverlayForPage( void QPDFJob::getUOPagenos( std::vector& uos, - std::map>>& pagenos) + std::vector>>& pagenos) { size_t uo_idx = 0; for (auto const& uo: uos) { size_t page_idx = 0; size_t from_size = uo.from_pagenos.size(); size_t repeat_size = uo.repeat_pagenos.size(); - for (int to_pageno: uo.to_pagenos) { + for (int to_pageno_i: uo.to_pagenos) { + size_t to_pageno = static_cast(to_pageno_i - 1); if (page_idx < from_size) { pagenos[to_pageno][uo_idx].push_back(uo.from_pagenos.at(page_idx)); } else if (repeat_size) { @@ -1978,67 +1945,47 @@ QPDFJob::handleUnderOverlay(QPDF& pdf) validateUnderOverlay(pdf, &uo); } - // First map key is 1-based page number. Second is index into the overlay/underlay vector. Watch - // out to not reverse the keys or be off by one. - std::map>> underlay_pagenos; - std::map>> overlay_pagenos; + auto const& dest_pages = pdf.getAllPages(); + + // First vector key is 0-based page number. Second is index into the overlay/underlay vector. + // Watch out to not reverse the keys or be off by one. + std::vector>> underlay_pagenos(dest_pages.size()); + std::vector>> overlay_pagenos(dest_pages.size()); getUOPagenos(m->underlay, underlay_pagenos); getUOPagenos(m->overlay, overlay_pagenos); doIfVerbose([&](Pipeline& v, std::string const& prefix) { v << prefix << ": processing underlay/overlay\n"; }); - auto get_pages = [](std::vector& v, - std::vector>& v_out) { - for (auto const& uo: v) { - if (uo.pdf) { - v_out.push_back(QPDFPageDocumentHelper(*(uo.pdf)).getAllPages()); - } - } - }; - std::vector> upages; - get_pages(m->underlay, upages); - std::vector> opages; - get_pages(m->overlay, opages); - std::map> underlay_fo; std::map> overlay_fo; - QPDFPageDocumentHelper main_pdh(pdf); - auto main_pages = main_pdh.getAllPages(); - size_t main_npages = main_pages.size(); - for (size_t page_idx = 0; page_idx < main_npages; ++page_idx) { - auto pageno = QIntC::to_int(page_idx) + 1; - doIfVerbose( - [&](Pipeline& v, std::string const& prefix) { v << " page " << pageno << "\n"; }); - if (underlay_pagenos[pageno].empty() && overlay_pagenos[pageno].empty()) { + PageNo dest_page_no; + for (QPDFPageObjectHelper dest_page: dest_pages) { + doIfVerbose([&](Pipeline& v, std::string const& prefix) { + v << " page " << dest_page_no.no << "\n"; + }); + if (underlay_pagenos[dest_page_no.idx].empty() && + overlay_pagenos[dest_page_no.idx].empty()) { + ++dest_page_no; continue; } // This code converts the original page, any underlays, and any overlays to form XObjects. // Then it concatenates display of all underlays, the original page, and all overlays. Prior // to 11.3.0, the original page contents were wrapped in q/Q, but this didn't work if the // original page had unbalanced q/Q operators. See GitHub issue #904. - auto& dest_page = main_pages.at(page_idx); - auto dest_page_oh = dest_page.getObjectHandle(); auto this_page_fo = dest_page.getFormXObjectForPage(); // The resulting form xobject lazily reads the content from the original page, which we are // going to replace. Therefore, we have to explicitly copy it. auto content_data = this_page_fo.getRawStreamData(); this_page_fo.replaceStreamData(content_data, QPDFObjectHandle(), QPDFObjectHandle()); - auto resources = - dest_page_oh.replaceKeyAndGetNew("/Resources", "<< /XObject << >> >>"_qpdf); - resources.getKey("/XObject").replaceKeyAndGetNew("/Fx0", this_page_fo); + auto resources = dest_page.getObjectHandle().replaceKeyAndGetNew( + "/Resources", Dictionary({{"/XObject", Dictionary({{"/Fx0", this_page_fo}})}})); + size_t uo_idx{0}; std::string content; for (auto& underlay: m->underlay) { content += doUnderOverlayForPage( - pdf, - underlay, - underlay_pagenos, - page_idx, - uo_idx, - underlay_fo, - upages[uo_idx], - dest_page); + pdf, underlay, underlay_pagenos, dest_page_no, uo_idx, underlay_fo, dest_page); ++uo_idx; } content += dest_page.placeFormXObject( @@ -2051,17 +1998,11 @@ QPDFJob::handleUnderOverlay(QPDF& pdf) uo_idx = 0; for (auto& overlay: m->overlay) { content += doUnderOverlayForPage( - pdf, - overlay, - overlay_pagenos, - page_idx, - uo_idx, - overlay_fo, - opages[uo_idx], - dest_page); + pdf, overlay, overlay_pagenos, dest_page_no, uo_idx, overlay_fo, dest_page); ++uo_idx; } - dest_page_oh.replaceKey("/Contents", pdf.newStream(content)); + dest_page.getObjectHandle().replaceKey("/Contents", pdf.newStream(content)); + ++dest_page_no; } } @@ -2389,108 +2330,200 @@ added_page(QPDF& pdf, QPDFPageObjectHelper page) return added_page(pdf, page.getObjectHandle()); } +// Initialize all members that depend on the QPDF object. If both qpdf and qpdf_p are null do +// nothing. void -QPDFJob::handlePageSpecs(QPDF& pdf, std::vector>& page_heap) +QPDFJob::Input::initialize(Inputs& in, QPDF* a_qpdf) { - // Parse all page specifications and translate them into lists of actual pages. + qpdf = a_qpdf ? a_qpdf : qpdf_p.get(); + if (qpdf) { + orig_pages = qpdf->getAllPages(); + n_pages = static_cast(orig_pages.size()); + copied_pages = std::vector(orig_pages.size(), false); - // Handle "." as a shortcut for the input file - for (auto& page_spec: m->page_specs) { - if (page_spec.filename == ".") { - page_spec.filename = m->infilename; + if (in.job.m->remove_unreferenced_page_resources != QPDFJob::re_no) { + remove_unreferenced = in.job.shouldRemoveUnreferencedResources(*qpdf); + } + if (qpdf->page_labels().hasPageLabels()) { + in.any_page_labels = true; } - if (page_spec.range.empty()) { - page_spec.range = "1-z"; + } +} + +void +QPDFJob::Inputs::infile_name(std::string const& name) +{ + if (!infile_name_.empty()) { + usage("input file has already been given"); + } + infile_name_ = name; + + auto& in_entry = *files.insert({name, Input()}).first; + auto it = files.find(""); + if (it != files.end()) { + // We allready have selection entries for the main input file. We need to fix them to point + // to the correct files entry. + for (auto& selection: selections) { + if (selection.in_entry == &*it) { + selection.in_entry = &in_entry; + } } + files.erase(it); + } +} + +void +QPDFJob::Inputs::process(std::string const& filename, QPDFJob::Input& input) +{ + // Open the PDF file and store the QPDF object. Do not canonicalize the file name. Using two + // different paths to refer to the same file is a documented workaround for duplicating a page. + // If you are using this an example of how to do this with the API, you can just create two + // different QPDF objects to the same underlying file with the same path to achieve the + // same effect. + auto password = input.password; + if (!encryption_file.empty() && password.empty() && filename == encryption_file) { + password = encryption_file_password; + } + job.doIfVerbose([&](Pipeline& v, std::string const& prefix) { + v << prefix << ": processing " << filename << "\n"; + }); + if (!keep_files_open) { + auto cis = std::make_shared(filename.data()); + input.cfis = cis.get(); + input.cfis->stayOpen(true); + job.processInputSource(input.qpdf_p, cis, password.data(), true); + } else { + job.processInputSource( + input.qpdf_p, + std::make_shared(filename.data()), + password.data(), + true); } + input.initialize(*this); - if (!m->keep_files_open_set) { + if (input.cfis) { + input.cfis->stayOpen(false); + } +} + +void +QPDFJob::Inputs::process_all() +{ + if (!infile_name().empty()) { + files.erase(""); + } + if (!keep_files_open_set) { // Count the number of distinct files to determine whether we should keep files open or not. // Rather than trying to code some portable heuristic based on OS limits, just hard-code // this at a given number and allow users to override. - std::set filenames; - for (auto& page_spec: m->page_specs) { - filenames.insert(page_spec.filename); - } - m->keep_files_open = (filenames.size() <= m->keep_files_open_threshold); - QTC::TC("qpdf", "QPDFJob automatically set keep files open", m->keep_files_open ? 0 : 1); - doIfVerbose([&](Pipeline& v, std::string const& prefix) { - v << prefix << ": selecting --keep-open-files=" << (m->keep_files_open ? "y" : "n") + keep_files_open = files.size() <= keep_files_open_threshold; + QTC::TC("qpdf", "QPDFJob automatically set keep files open", keep_files_open ? 0 : 1); + job.doIfVerbose([&](Pipeline& v, std::string const& prefix) { + v << prefix << ": selecting --keep-open-files=" << (keep_files_open ? "y" : "n") << "\n"; }); } - // Create a QPDF object for each file that we may take pages from. - std::map page_spec_qpdfs; - std::map page_spec_cfis; - page_spec_qpdfs[m->infilename] = &pdf; - std::vector parsed_specs; - std::map> copied_pages; - for (auto& page_spec: m->page_specs) { - if (!page_spec_qpdfs.contains(page_spec.filename)) { - // Open the PDF file and store the QPDF object. Throw a std::shared_ptr to the qpdf into - // a heap so that it survives through copying to the output but gets cleaned up - // automatically at the end. Do not canonicalize the file name. Using two different - // paths to refer to the same file is a documented workaround for duplicating a page. If - // you are using this an example of how to do this with the API, you can just create two - // different QPDF objects to the same underlying file with the same path to achieve the - // same effect. - auto password = page_spec.password; - if (!m->encryption_file.empty() && password.empty() && - page_spec.filename == m->encryption_file) { - QTC::TC("qpdf", "QPDFJob pages encryption password"); - password = m->encryption_file_password; + for (auto& [filename, input]: files) { + if (!input.qpdf) { + process(filename, input); + } + + for (auto& selection: selections) { + if (&selection.input() != &input) { + continue; } - doIfVerbose([&](Pipeline& v, std::string const& prefix) { - v << prefix << ": processing " << page_spec.filename << "\n"; - }); - std::shared_ptr is; - ClosedFileInputSource* cis = nullptr; - if (!m->keep_files_open) { - QTC::TC("qpdf", "QPDFJob keep files open n"); - cis = new ClosedFileInputSource(page_spec.filename.c_str()); - is = std::shared_ptr(cis); - cis->stayOpen(true); - } else { - QTC::TC("qpdf", "QPDFJob keep files open y"); - FileInputSource* fis = new FileInputSource(page_spec.filename.c_str()); - is = std::shared_ptr(fis); + // Read original pages from the PDF, and parse the page range associated with this + // occurrence of the file. + if (selection.range.empty()) { + selection.selected_pages.reserve(static_cast(input.n_pages)); + for (int i = 1; i <= input.n_pages; ++i) { + selection.selected_pages.push_back(i); + } + continue; } - std::unique_ptr qpdf_sp; - processInputSource(qpdf_sp, is, password.data(), true); - page_spec_qpdfs[page_spec.filename] = qpdf_sp.get(); - page_heap.push_back(std::move(qpdf_sp)); - if (cis) { - cis->stayOpen(false); - page_spec_cfis[page_spec.filename] = cis; + try { + selection.selected_pages = + QUtil::parse_numrange(selection.range.data(), selection.input().n_pages); + } catch (std::runtime_error& e) { + throw std::runtime_error( + "parsing numeric range for " + selection.filename() + ": " + e.what()); } } - - // Read original pages from the PDF, and parse the page range associated with this - // occurrence of the file. - parsed_specs.emplace_back( - page_spec.filename, page_spec_qpdfs[page_spec.filename], page_spec.range); } +} - std::map remove_unreferenced; - if (m->remove_unreferenced_page_resources != QPDFJob::re_no) { - for (auto const& iter: page_spec_qpdfs) { - std::string const& filename = iter.first; - ClosedFileInputSource* cis = nullptr; - if (page_spec_cfis.contains(filename)) { - cis = page_spec_cfis[filename]; - cis->stayOpen(true); - } - QPDF& other(*(iter.second)); - auto other_uuid = other.getUniqueId(); - if (!remove_unreferenced.contains(other_uuid)) { - remove_unreferenced[other_uuid] = shouldRemoveUnreferencedResources(other); - } - if (cis) { - cis->stayOpen(false); - } +bool +QPDFJob::Inputs::clear() +{ + bool any_warnings = false; + for (auto& [filename, file_spec]: files) { + if (auto& pdf = file_spec.qpdf_p) { + any_warnings |= pdf->anyWarnings(); + pdf = nullptr; } } + return any_warnings; +} + +QPDFJob::Selection& +QPDFJob::Inputs::new_selection(std::string const& filename) +{ + // Handle "." as a shortcut for the input file. Note that infile_name may not be known yet, in + // which case we are wrongly entering an empty name. This will be corrected in the infile_name + // setter. + return selections.emplace_back( + *files.insert({(filename == "." ? infile_name() : filename), Input()}).first); +} + +void +QPDFJob::Inputs::new_selection( + std::string const& filename, std::string const& password, std::string const& range) +{ + auto& selection = new_selection(filename); + selection.password(password); + selection.range = range; +} + +QPDFJob::Selection::Selection(std::pair& entry) : + in_entry(&entry) +{ +} + +QPDFJob::Input& +QPDFJob::Selection::input() +{ + return in_entry->second; +} + +std::string const& +QPDFJob::Selection::filename() +{ + return in_entry->first; +} + +void +QPDFJob::Selection::password(std::string password) +{ + auto& in = input(); + if (!in.password.empty()) { + usage("--password already specified for this file"); + } + in.password = password; +} + +// Handle all page specifications. +void +QPDFJob::handlePageSpecs(QPDF& pdf) +{ + if (m->inputs.selections.empty()) { + return; + } + auto& main_input = m->inputs.files[m->infile_name()]; + main_input.initialize(m->inputs, &pdf); + + // Parse all section and translate them into lists of actual pages. + m->inputs.process_all(); // Clear all pages out of the primary QPDF's pages tree but leave the objects in place in the // file so they can be re-added without changing their object numbers. This enables other things @@ -2498,23 +2531,22 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector>& page_hea doIfVerbose([&](Pipeline& v, std::string const& prefix) { v << prefix << ": removing unreferenced pages from primary input\n"; }); - QPDFPageDocumentHelper dh(pdf); - std::vector orig_pages = dh.getAllPages(); - for (auto const& page: orig_pages) { - dh.removePage(page); + for (auto const& page: main_input.orig_pages) { + pdf.removePage(page); } auto n_collate = m->collate.size(); - auto n_specs = parsed_specs.size(); + auto n_specs = m->inputs.selections.size(); if (!(n_collate == 0 || n_collate == 1 || n_collate == n_specs)) { usage( "--pages: if --collate has more than one value, it must have one value per page " "specification"); } + + std::vector new_specs; if (n_collate > 0 && n_specs > 1) { // Collate the pages by selecting one page from each spec in order. When a spec runs out of // pages, stop selecting from it. - std::vector new_parsed_specs; // Make sure we have a collate value for each spec. We have already checked that a non-empty // collate has either one value or one value per spec. for (auto i = n_collate; i < n_specs; ++i) { @@ -2525,70 +2557,55 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector>& page_hea while (got_pages) { got_pages = false; for (size_t i = 0; i < n_specs; ++i) { - QPDFPageData& page_data = parsed_specs.at(i); + auto& page_data = m->inputs.selections.at(i); for (size_t j = 0; j < m->collate.at(i); ++j) { if (cur_page.at(i) + j < page_data.selected_pages.size()) { got_pages = true; - new_parsed_specs.emplace_back( + new_specs.emplace_back( page_data, page_data.selected_pages.at(cur_page.at(i) + j)); } } cur_page.at(i) += m->collate.at(i); } } - parsed_specs = new_parsed_specs; } // Add all the pages from all the files in the order specified. Keep track of any pages from the // original file that we are selecting. - std::set selected_from_orig; std::vector new_labels; - bool any_page_labels = false; int out_pageno = 0; auto& this_afdh = pdf.acroform(); std::set referenced_fields; - for (auto& page_data: parsed_specs) { - ClosedFileInputSource* cis = nullptr; - if (page_spec_cfis.contains(page_data.filename)) { - cis = page_spec_cfis[page_data.filename]; - cis->stayOpen(true); - } - auto& pldh = page_data.qpdf->page_labels(); - auto& other_afdh = page_data.qpdf->acroform(); - if (pldh.hasPageLabels()) { - any_page_labels = true; + for (auto& selection: new_specs.empty() ? m->inputs.selections : new_specs) { + auto& input = selection.input(); + if (input.cfis) { + input.cfis->stayOpen(true); } + auto* pldh = m->inputs.any_page_labels ? &input.qpdf->page_labels() : nullptr; + auto& other_afdh = input.qpdf->acroform(); doIfVerbose([&](Pipeline& v, std::string const& prefix) { - v << prefix << ": adding pages from " << page_data.filename << "\n"; + v << prefix << ": adding pages from " << selection.filename() << "\n"; }); - for (auto pageno_iter: page_data.selected_pages) { + const bool this_file = input.qpdf == &pdf; + for (PageNo page: selection.selected_pages) { + bool first_copy_from_orig = this_file && !main_input.copied_pages[page.idx]; + // Pages are specified from 1 but numbered from 0 in the vector - int pageno = pageno_iter - 1; - pldh.getLabelsForPageRange(pageno, pageno, out_pageno++, new_labels); - QPDFPageObjectHelper to_copy = page_data.orig_pages.at(QIntC::to_size(pageno)); - QPDFObjGen to_copy_og = to_copy.getObjectHandle().getObjGen(); - unsigned long long from_uuid = page_data.qpdf->getUniqueId(); - if (copied_pages[from_uuid].contains(to_copy_og)) { - QTC::TC( - "qpdf", - "QPDFJob copy same page more than once", - (page_data.qpdf == &pdf) ? 0 : 1); + int pageno = page.no - 1; + if (pldh) { + pldh->getLabelsForPageRange(pageno, pageno, out_pageno++, new_labels); + } + QPDFPageObjectHelper to_copy = input.orig_pages.at(page.idx); + if (input.copied_pages[page.idx]) { + QTC::TC("qpdf", "QPDFJob copy same page more than once", this_file ? 0 : 1); to_copy = to_copy.shallowCopyPage(); } else { - copied_pages[from_uuid].insert(to_copy_og); - if (remove_unreferenced[from_uuid]) { + input.copied_pages[page.idx] = true; + if (input.remove_unreferenced) { to_copy.removeUnreferencedResources(); } } - dh.addPage(to_copy, false); - bool first_copy_from_orig = false; - bool this_file = (page_data.qpdf == &pdf); - if (this_file) { - // This is a page from the original file. Keep track of the fact that we are using - // it. - first_copy_from_orig = (!selected_from_orig.contains(pageno)); - selected_from_orig.insert(pageno); - } + pdf.addPage(to_copy, false); auto new_page = added_page(pdf, to_copy); // Try to avoid gratuitously renaming fields. In the case of where we're just extracting // a bunch of pages from the original file and not copying any page more than once, @@ -2616,48 +2633,46 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector>& page_hea } } } - if (cis) { - cis->stayOpen(false); + if (input.cfis) { + input.cfis->stayOpen(false); } } - if (any_page_labels) { - QPDFObjectHandle page_labels = QPDFObjectHandle::newDictionary(); - page_labels.replaceKey("/Nums", QPDFObjectHandle::newArray(new_labels)); - pdf.getRoot().replaceKey("/PageLabels", page_labels); + if (m->inputs.any_page_labels) { + pdf.getRoot().replaceKey("/PageLabels", Dictionary({{"/Nums", Array(new_labels)}})); } // Delete page objects for unused page in primary. This prevents those objects from being // preserved by being referred to from other places, such as the outlines dictionary. Also make // sure we keep form fields from pages we preserved. - for (size_t pageno = 0; pageno < orig_pages.size(); ++pageno) { - auto page = orig_pages.at(pageno); - if (selected_from_orig.contains(QIntC::to_int(pageno))) { + size_t page_idx = 0; + for (auto const& page: main_input.orig_pages) { + if (main_input.copied_pages[page_idx]) { for (auto field: this_afdh.getFormFieldsForPage(page)) { - QTC::TC("qpdf", "QPDFJob pages keeping field from original"); - referenced_fields.insert(field.getObjectHandle().getObjGen()); + referenced_fields.insert(field); } } else { - pdf.replaceObject(page.getObjectHandle().getObjGen(), QPDFObjectHandle::newNull()); + pdf.replaceObject(page, QPDFObjectHandle::newNull()); } + ++page_idx; } // Remove unreferenced form fields if (this_afdh.hasAcroForm()) { - auto acroform = pdf.getRoot().getKey("/AcroForm"); - auto fields = acroform.getKey("/Fields"); - if (fields.isArray()) { - auto new_fields = QPDFObjectHandle::newArray(); - if (fields.isIndirect()) { - new_fields = pdf.makeIndirectObject(new_fields); - } - for (auto const& field: fields.aitems()) { + auto acroform = pdf.getRoot()["/AcroForm"]; + if (Array fields = acroform["/Fields"]) { + std::vector new_fields; + new_fields.reserve(referenced_fields.size()); + for (auto const& field: fields) { if (referenced_fields.contains(field.getObjGen())) { - new_fields.appendItem(field); + new_fields.emplace_back(field); } } if (new_fields.empty()) { - pdf.getRoot().removeKey("/AcroForm"); + pdf.getRoot().erase("/AcroForm"); } else { - acroform.replaceKey("/Fields", new_fields); + acroform.replaceKey( + "/Fields", + fields.indirect() ? pdf.makeIndirectObject(Array(new_fields)) + : QPDFObjectHandle(Array(new_fields))); } } } @@ -2924,8 +2939,8 @@ QPDFJob::setWriterOptions(QPDFWriter& w) std::unique_ptr encryption_pdf; processFile( encryption_pdf, - m->encryption_file.data(), - m->encryption_file_password.data(), + m->inputs.encryption_file.data(), + m->inputs.encryption_file_password.data(), false, false); w.copyEncryptionParameters(*encryption_pdf); @@ -3045,7 +3060,7 @@ QPDFJob::doSplitPages(QPDF& pdf) page_range += "-" + QUtil::uint_to_string(last, QIntC::to_int(pageno_len)); } std::string outfile = before + page_range + after; - if (QUtil::same_file(m->infilename.data(), outfile.data())) { + if (QUtil::same_file(m->infile_nm(), outfile.data())) { throw std::runtime_error("split pages would overwrite input file with " + outfile); } QPDFWriter w(outpdf, outfile.c_str()); @@ -3064,7 +3079,7 @@ QPDFJob::writeOutfile(QPDF& pdf) if (m->replace_input) { // Append but don't prepend to the path to generate a temporary name. This saves us from // having to split the path by directory and non-directory. - temp_out = m->infilename + ".~qpdf-temp#"; + temp_out = m->infile_name() + ".~qpdf-temp#"; // m->outfilename will be restored to 0 before temp_out goes out of scope. m->outfilename = temp_out; } else if (m->outfilename == "-") { @@ -3098,13 +3113,13 @@ QPDFJob::writeOutfile(QPDF& pdf) if (m->replace_input) { // We must close the input before we can rename files pdf.closeInputSource(); - std::string backup = m->infilename + ".~qpdf-orig"; + std::string backup = m->infile_name() + ".~qpdf-orig"; bool warnings = pdf.anyWarnings(); if (!warnings) { backup.append(1, '#'); } - QUtil::rename_file(m->infilename.data(), backup.data()); - QUtil::rename_file(temp_out.data(), m->infilename.data()); + QUtil::rename_file(m->infile_nm(), backup.data()); + QUtil::rename_file(temp_out.data(), m->infile_nm()); if (warnings) { *m->log->getError() << m->message_prefix << ": there are warnings; original file kept in " << backup << "\n"; diff --git a/libqpdf/QPDFJob_config.cc b/libqpdf/QPDFJob_config.cc index 1b548f4..746b5be 100644 --- a/libqpdf/QPDFJob_config.cc +++ b/libqpdf/QPDFJob_config.cc @@ -1,4 +1,4 @@ -#include +#include #include @@ -15,18 +15,14 @@ QPDFJob::Config::checkConfiguration() QPDFJob::Config* QPDFJob::Config::inputFile(std::string const& filename) { - if (o.m->infilename.empty()) { - o.m->infilename = filename; - } else { - usage("input file has already been given"); - } + o.m->inputs.infile_name(filename); return this; } QPDFJob::Config* QPDFJob::Config::emptyInput() { - if (o.m->infilename.empty()) { + if (o.m->infile_name().empty()) { // Various places in QPDFJob.cc used to know that the empty string for infile means empty. // This approach meant that passing "" as the argument to inputFile in job JSON, or // equivalently using "" as a positional command-line argument would be the same as @@ -152,7 +148,7 @@ QPDFJob::Config::copyEncryption(std::string const& parameter) if (o.m->deterministic_id) { usage("the deterministic-id option is incompatible with encrypted output files"); } - o.m->encryption_file = parameter; + o.m->inputs.encryption_file = parameter; o.m->copy_encryption = true; o.m->encrypt = false; o.m->decrypt = false; @@ -181,7 +177,7 @@ QPDFJob::Config::deterministicId() QPDFJob::Config* QPDFJob::Config::encryptionFilePassword(std::string const& parameter) { - o.m->encryption_file_password = parameter; + o.m->inputs.encryption_file_password = parameter; return this; } @@ -354,15 +350,15 @@ QPDFJob::Config::testJsonSchema() QPDFJob::Config* QPDFJob::Config::keepFilesOpen(std::string const& parameter) { - o.m->keep_files_open_set = true; - o.m->keep_files_open = (parameter == "y"); + o.m->inputs.keep_files_open_set = true; + o.m->inputs.keep_files_open = (parameter == "y"); return this; } QPDFJob::Config* QPDFJob::Config::keepFilesOpenThreshold(std::string const& parameter) { - o.m->keep_files_open_threshold = QUtil::string_to_uint(parameter.c_str()); + o.m->inputs.keep_files_open_threshold = QUtil::string_to_uint(parameter.c_str()); return this; } @@ -978,7 +974,7 @@ QPDFJob::PagesConfig::PagesConfig(Config* c) : std::shared_ptr QPDFJob::Config::pages() { - if (!o.m->page_specs.empty()) { + if (!o.m->inputs.selections.empty()) { usage("--pages may only be specified one time"); } return std::shared_ptr(new PagesConfig(this)); @@ -987,7 +983,7 @@ QPDFJob::Config::pages() QPDFJob::Config* QPDFJob::PagesConfig::endPages() { - auto n_specs = config->o.m->page_specs.size(); + auto n_specs = config->o.m->inputs.selections.size(); if (n_specs == 0) { usage("--pages: no page specifications given"); } @@ -998,27 +994,25 @@ QPDFJob::PagesConfig* QPDFJob::PagesConfig::pageSpec( std::string const& filename, std::string const& range, char const* password) { - config->o.m->page_specs.emplace_back(filename, password, range); + config->o.m->inputs.new_selection(filename, {password ? password : ""}, range); return this; } QPDFJob::PagesConfig* QPDFJob::PagesConfig::file(std::string const& arg) { - config->o.m->page_specs.emplace_back(arg, "", ""); + (void)config->o.m->inputs.new_selection(arg); return this; } QPDFJob::PagesConfig* QPDFJob::PagesConfig::range(std::string const& arg) { - if (config->o.m->page_specs.empty()) { - QTC::TC("qpdf", "QPDFJob misplaced page range"); + if (config->o.m->inputs.selections.empty()) { usage("in --range must follow a file name"); } - auto& last = config->o.m->page_specs.back(); + auto& last = config->o.m->inputs.selections.back(); if (!last.range.empty()) { - QTC::TC("qpdf", "QPDFJob duplicated range"); usage("--range already specified for this file"); } last.range = arg; @@ -1028,16 +1022,10 @@ QPDFJob::PagesConfig::range(std::string const& arg) QPDFJob::PagesConfig* QPDFJob::PagesConfig::password(std::string const& arg) { - if (config->o.m->page_specs.empty()) { - QTC::TC("qpdf", "QPDFJob misplaced pages password"); + if (config->o.m->inputs.selections.empty()) { usage("in --pages, --password must follow a file name"); } - auto& last = config->o.m->page_specs.back(); - if (!last.password.empty()) { - QTC::TC("qpdf", "QPDFJob duplicated pages password"); - usage("--password already specified for this file"); - } - last.password = arg; + config->o.m->inputs.selections.back().password(arg); return this; } diff --git a/libqpdf/QPDFJob_json.cc b/libqpdf/QPDFJob_json.cc index c4333d0..1684359 100644 --- a/libqpdf/QPDFJob_json.cc +++ b/libqpdf/QPDFJob_json.cc @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/libqpdf/qpdf/QPDFJob_private.hh b/libqpdf/qpdf/QPDFJob_private.hh new file mode 100644 index 0000000..5f80fd6 --- /dev/null +++ b/libqpdf/qpdf/QPDFJob_private.hh @@ -0,0 +1,311 @@ +#ifndef QPDFJOB_PRIVATE_HH +#define QPDFJOB_PRIVATE_HH + +#include + +#include +#include + +// A selection of pages from a single input PDF to be included in the output. This corresponds to a +// single clause in the --pages option. +struct QPDFJob::Selection +{ + Selection() = delete; + + Selection(std::pair& entry); + + Selection(Selection const& other, int page) : + in_entry(other.in_entry), + selected_pages({page}) + { + } + + QPDFJob::Input& input(); + std::string const& filename(); + void password(std::string password); + + std::pair* in_entry{nullptr}; + std::string range; // An empty range means all pages. + std::vector selected_pages; +}; + +// A single input PDF. +// +// N.B. A single input PDF may be represented by multiple Input instances using variations of the +// filename. This is a documented work-around. +struct QPDFJob::Input +{ + void initialize(Inputs& in, QPDF* qpdf = nullptr); + + std::string password; + std::unique_ptr qpdf_p; + QPDF* qpdf; + ClosedFileInputSource* cfis{}; + std::vector orig_pages; + int n_pages; + std::vector copied_pages; + bool remove_unreferenced{false}; +}; + +// All PDF input files for a job. +struct QPDFJob::Inputs +{ + friend struct Input; + // These default values are duplicated in help and docs. + static int constexpr DEFAULT_KEEP_FILES_OPEN_THRESHOLD = 200; + + Inputs(QPDFJob& job) : + job(job) + { + } + void process(std::string const& filename, QPDFJob::Input& file_spec); + void process_all(); + + // Destroy all owned QPDF objects. Return false if any of the QPDF objects recorded warnings. + bool clear(); + + Selection& new_selection(std::string const& filename); + + void new_selection( + std::string const& filename, std::string const& password, std::string const& range); + + std::string const& + infile_name() const + { + return infile_name_; + } + + void infile_name(std::string const& name); + + std::string infile_name_; + std::string encryption_file; + std::string encryption_file_password; + bool keep_files_open{true}; + bool keep_files_open_set{false}; + size_t keep_files_open_threshold{DEFAULT_KEEP_FILES_OPEN_THRESHOLD}; + + std::map files; + std::vector selections; + + bool any_page_labels{false}; + + private: + QPDFJob& job; +}; + +struct QPDFJob::RotationSpec +{ + RotationSpec(int angle = 0, bool relative = false) : + angle(angle), + relative(relative) + { + } + + int angle; + bool relative; +}; + +struct QPDFJob::UnderOverlay +{ + UnderOverlay(char const* which) : + which(which), + to_nr("1-z"), + from_nr("1-z"), + repeat_nr("") + { + } + + std::string which; + std::string filename; + std::string password; + std::string to_nr; + std::string from_nr; + std::string repeat_nr; + std::unique_ptr pdf; + std::vector to_pagenos; + std::vector from_pagenos; + std::vector repeat_pagenos; +}; + +struct QPDFJob::PageLabelSpec +{ + PageLabelSpec( + int first_page, qpdf_page_label_e label_type, int start_num, std::string_view prefix) : + first_page(first_page), + label_type(label_type), + start_num(start_num), + prefix(prefix) + { + } + int first_page; + qpdf_page_label_e label_type; + int start_num{1}; + std::string prefix; +}; + +class QPDFJob::Members +{ + friend class QPDFJob; + + public: + Members(QPDFJob& job) : + log(QPDFLogger::defaultLogger()), + inputs(job) + { + } + Members(Members const&) = delete; + ~Members() = default; + + inline const char* infile_nm() const; + + inline std::string const& infile_name() const; + + private: + // These default values are duplicated in help and docs. + static int constexpr DEFAULT_OI_MIN_WIDTH = 128; + static int constexpr DEFAULT_OI_MIN_HEIGHT = 128; + static int constexpr DEFAULT_OI_MIN_AREA = 16384; + static int constexpr DEFAULT_II_MIN_BYTES = 1024; + + std::shared_ptr log; + std::string message_prefix{"qpdf"}; + bool warnings{false}; + unsigned long encryption_status{0}; + bool verbose{false}; + std::string password; + bool linearize{false}; + bool decrypt{false}; + bool remove_restrictions{false}; + int split_pages{0}; + bool progress{false}; + std::function progress_handler{nullptr}; + bool suppress_warnings{false}; + bool warnings_exit_zero{false}; + bool copy_encryption{false}; + bool encrypt{false}; + bool password_is_hex_key{false}; + bool suppress_password_recovery{false}; + password_mode_e password_mode{pm_auto}; + bool allow_insecure{false}; + bool allow_weak_crypto{false}; + std::string user_password; + std::string owner_password; + int keylen{0}; + bool r2_print{true}; + bool r2_modify{true}; + bool r2_extract{true}; + bool r2_annotate{true}; + bool r3_accessibility{true}; + bool r3_extract{true}; + bool r3_assemble{true}; + bool r3_annotate_and_form{true}; + bool r3_form_filling{true}; + bool r3_modify_other{true}; + qpdf_r3_print_e r3_print{qpdf_r3p_full}; + bool force_V4{false}; + bool force_R5{false}; + bool cleartext_metadata{false}; + bool use_aes{false}; + bool stream_data_set{false}; + qpdf_stream_data_e stream_data_mode{qpdf_s_compress}; + bool compress_streams{true}; + bool compress_streams_set{false}; + bool recompress_flate{false}; + bool recompress_flate_set{false}; + int compression_level{-1}; + int jpeg_quality{-1}; + qpdf_stream_decode_level_e decode_level{qpdf_dl_generalized}; + bool decode_level_set{false}; + bool normalize_set{false}; + bool normalize{false}; + bool suppress_recovery{false}; + bool object_stream_set{false}; + qpdf_object_stream_e object_stream_mode{qpdf_o_preserve}; + bool ignore_xref_streams{false}; + bool qdf_mode{false}; + bool preserve_unreferenced_objects{false}; + remove_unref_e remove_unreferenced_page_resources{re_auto}; + bool newline_before_endstream{false}; + std::string linearize_pass1; + bool coalesce_contents{false}; + bool flatten_annotations{false}; + int flatten_annotations_required{0}; + int flatten_annotations_forbidden{an_invisible | an_hidden}; + bool generate_appearances{false}; + PDFVersion max_input_version; + std::string min_version; + std::string force_version; + bool show_npages{false}; + bool deterministic_id{false}; + bool static_id{false}; + bool static_aes_iv{false}; + bool suppress_original_object_id{false}; + bool show_encryption{false}; + bool show_encryption_key{false}; + bool check_linearization{false}; + bool show_linearization{false}; + bool show_xref{false}; + bool show_trailer{false}; + int show_obj{0}; + int show_gen{0}; + bool show_raw_stream_data{false}; + bool show_filtered_stream_data{false}; + bool show_pages{false}; + bool show_page_images{false}; + std::vector collate; + bool flatten_rotation{false}; + bool list_attachments{false}; + std::string attachment_to_show; + std::list attachments_to_remove; + std::list attachments_to_add; + std::list attachments_to_copy; + int json_version{0}; + std::set json_keys; + std::set json_objects; + qpdf_json_stream_data_e json_stream_data{qpdf_sj_none}; + bool json_stream_data_set{false}; + std::string json_stream_prefix; + bool test_json_schema{false}; + bool check{false}; + bool optimize_images{false}; + bool externalize_inline_images{false}; + bool keep_inline_images{false}; + bool remove_info{false}; + bool remove_metadata{false}; + bool remove_page_labels{false}; + bool remove_structure{false}; + size_t oi_min_width{DEFAULT_OI_MIN_WIDTH}; + size_t oi_min_height{DEFAULT_OI_MIN_HEIGHT}; + size_t oi_min_area{DEFAULT_OI_MIN_AREA}; + size_t ii_min_bytes{DEFAULT_II_MIN_BYTES}; + std::vector underlay; + std::vector overlay; + UnderOverlay* under_overlay{nullptr}; + Inputs inputs; + std::map rotations; + bool require_outfile{true}; + bool replace_input{false}; + bool check_is_encrypted{false}; + bool check_requires_password{false}; + bool empty_input{false}; + std::string outfilename; + bool json_input{false}; + bool json_output{false}; + std::string update_from_json; + bool report_mem_usage{false}; + std::vector page_label_specs; +}; + +inline const char* +QPDFJob::Members::infile_nm() const +{ + return inputs.infile_name().data(); +} + +inline std::string const& +QPDFJob::Members::infile_name() const +{ + return inputs.infile_name(); +} + +#endif // QPDFJOB_PRIVATE_HH diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index cf09e24..31f6b21 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -188,7 +188,6 @@ QPDF insert foreign page 0 QPDFWriter copy use_aes 1 QPDFParser indirect without context 0 QPDFObjectHandle trailing data in parse 0 -QPDFJob pages encryption password 0 QPDFTokenizer EOF reading token 0 QPDFTokenizer EOF reading appendable token 0 QPDFWriter extra header text no newline 0 @@ -209,7 +208,6 @@ QPDF not caching overridden objstm object 0 QPDF_optimization indirect outlines 0 QPDF xref space 2 QPDFJob pages range omitted in middle 0 -QPDFJob npages 0 QPDFWriter standard deterministic ID 1 QPDFWriter linearized deterministic ID 1 qpdf-c called qpdf_set_deterministic_ID 0 @@ -222,7 +220,6 @@ QPDFParser found fake 1 QPDFParser no val for last key 0 QPDF resolve failure to null 0 QPDFObjectHandle errors in parsecontent 0 -QPDFJob same file error 0 QPDFJob split-pages %d 0 QPDFJob split-pages .pdf 0 QPDFJob split-pages other 0 @@ -286,8 +283,6 @@ QPDFAcroFormDocumentHelper non-dictionary field 0 QPDFAcroFormDocumentHelper loop 0 QPDFAcroFormDocumentHelper field found 1 QPDFAcroFormDocumentHelper annotation found 1 -QPDFJob keep files open n 0 -QPDFJob keep files open y 0 QPDFJob automatically set keep files open 1 QPDFOutlineDocumentHelper string named dest 0 QPDFObjectHandle merge top type mismatch 0 @@ -338,7 +333,6 @@ QPDFPageDocumentHelper ignore annotation with no appearance 0 QPDFFormFieldObjectHelper replaced BMC at EOF 0 QPDFFormFieldObjectHelper fallback Tf 0 QPDFPageObjectHelper copy shared attribute 1 -QPDFJob from_nr from repeat_nr 0 QPDF resolve duplicated page object 0 QPDF handle direct page object 0 QPDF missing mediabox 0 @@ -453,7 +447,6 @@ QPDFFileSpecObjectHelper empty compat_name 0 QPDFFileSpecObjectHelper non-empty compat_name 0 QPDFAcroFormDocumentHelper copy annotation 3 QPDFAcroFormDocumentHelper field with parent 3 -QPDFJob pages keeping field from original 0 QPDFObjectHandle merge reuse 0 QPDFObjectHandle merge generate 0 QPDFAcroFormDocumentHelper replaced DA token 0 @@ -493,8 +486,6 @@ qpdf-c called qpdf_oh_get_binary_string_value 0 qpdf-c called qpdf_oh_get_binary_utf8_value 0 qpdf-c called qpdf_oh_new_binary_string 0 qpdf-c called qpdf_oh_new_binary_unicode_string 0 -QPDFJob duplicated pages password 0 -QPDFJob misplaced pages password 0 QPDFJob check encrypted encrypted 0 QPDFJob check encrypted not encrypted 0 QPDFJob check password password incorrect 0 @@ -547,8 +538,6 @@ QPDFPageObjectHelper used fallback without copying 0 QPDF skipping cache for known unchecked object 0 QPDF fix dangling triggered xref reconstruction 0 QPDF recover xref stream 0 -QPDFJob misplaced page range 0 -QPDFJob duplicated range 0 QPDFJob json over/under no file 0 QPDF_Array copy 1 QPDF_json stream data not string 0 diff --git a/qpdf/qtest/qpdf/disable-kfo.out b/qpdf/qtest/qpdf/disable-kfo.out index f8f4622..f68a2ca 100644 --- a/qpdf/qtest/qpdf/disable-kfo.out +++ b/qpdf/qtest/qpdf/disable-kfo.out @@ -1,157 +1,157 @@ -qpdf: selecting --keep-open-files=n -qpdf: processing 001-kfo.pdf -qpdf: processing 002-kfo.pdf -qpdf: processing 003-kfo.pdf -qpdf: processing 004-kfo.pdf -qpdf: processing 005-kfo.pdf -qpdf: processing 006-kfo.pdf -qpdf: processing 007-kfo.pdf -qpdf: processing 008-kfo.pdf -qpdf: processing 009-kfo.pdf -qpdf: processing 010-kfo.pdf -qpdf: processing 011-kfo.pdf -qpdf: processing 012-kfo.pdf -qpdf: processing 013-kfo.pdf -qpdf: processing 014-kfo.pdf -qpdf: processing 015-kfo.pdf -qpdf: processing 016-kfo.pdf -qpdf: processing 017-kfo.pdf -qpdf: processing 018-kfo.pdf -qpdf: processing 019-kfo.pdf -qpdf: processing 020-kfo.pdf -qpdf: processing 021-kfo.pdf -qpdf: processing 022-kfo.pdf -qpdf: processing 023-kfo.pdf -qpdf: processing 024-kfo.pdf -qpdf: processing 025-kfo.pdf -qpdf: processing 026-kfo.pdf -qpdf: processing 027-kfo.pdf -qpdf: processing 028-kfo.pdf -qpdf: processing 029-kfo.pdf -qpdf: processing 030-kfo.pdf -qpdf: processing 031-kfo.pdf -qpdf: processing 032-kfo.pdf -qpdf: processing 033-kfo.pdf -qpdf: processing 034-kfo.pdf -qpdf: processing 035-kfo.pdf -qpdf: processing 036-kfo.pdf -qpdf: processing 037-kfo.pdf -qpdf: processing 038-kfo.pdf -qpdf: processing 039-kfo.pdf -qpdf: processing 040-kfo.pdf -qpdf: processing 041-kfo.pdf -qpdf: processing 042-kfo.pdf -qpdf: processing 043-kfo.pdf -qpdf: processing 044-kfo.pdf -qpdf: processing 045-kfo.pdf -qpdf: processing 046-kfo.pdf -qpdf: processing 047-kfo.pdf -qpdf: processing 048-kfo.pdf -qpdf: processing 049-kfo.pdf -qpdf: processing 050-kfo.pdf -qpdf: processing 051-kfo.pdf qpdf: empty PDF: checking for shared resources qpdf: no shared resources found +qpdf: selecting --keep-open-files=n +qpdf: processing 001-kfo.pdf qpdf: 001-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 002-kfo.pdf qpdf: 002-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 003-kfo.pdf qpdf: 003-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 004-kfo.pdf qpdf: 004-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 005-kfo.pdf qpdf: 005-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 006-kfo.pdf qpdf: 006-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 007-kfo.pdf qpdf: 007-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 008-kfo.pdf qpdf: 008-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 009-kfo.pdf qpdf: 009-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 010-kfo.pdf qpdf: 010-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 011-kfo.pdf qpdf: 011-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 012-kfo.pdf qpdf: 012-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 013-kfo.pdf qpdf: 013-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 014-kfo.pdf qpdf: 014-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 015-kfo.pdf qpdf: 015-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 016-kfo.pdf qpdf: 016-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 017-kfo.pdf qpdf: 017-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 018-kfo.pdf qpdf: 018-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 019-kfo.pdf qpdf: 019-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 020-kfo.pdf qpdf: 020-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 021-kfo.pdf qpdf: 021-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 022-kfo.pdf qpdf: 022-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 023-kfo.pdf qpdf: 023-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 024-kfo.pdf qpdf: 024-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 025-kfo.pdf qpdf: 025-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 026-kfo.pdf qpdf: 026-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 027-kfo.pdf qpdf: 027-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 028-kfo.pdf qpdf: 028-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 029-kfo.pdf qpdf: 029-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 030-kfo.pdf qpdf: 030-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 031-kfo.pdf qpdf: 031-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 032-kfo.pdf qpdf: 032-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 033-kfo.pdf qpdf: 033-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 034-kfo.pdf qpdf: 034-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 035-kfo.pdf qpdf: 035-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 036-kfo.pdf qpdf: 036-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 037-kfo.pdf qpdf: 037-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 038-kfo.pdf qpdf: 038-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 039-kfo.pdf qpdf: 039-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 040-kfo.pdf qpdf: 040-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 041-kfo.pdf qpdf: 041-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 042-kfo.pdf qpdf: 042-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 043-kfo.pdf qpdf: 043-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 044-kfo.pdf qpdf: 044-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 045-kfo.pdf qpdf: 045-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 046-kfo.pdf qpdf: 046-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 047-kfo.pdf qpdf: 047-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 048-kfo.pdf qpdf: 048-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 049-kfo.pdf qpdf: 049-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 050-kfo.pdf qpdf: 050-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 051-kfo.pdf qpdf: 051-kfo.pdf: checking for shared resources qpdf: no shared resources found qpdf: removing unreferenced pages from primary input diff --git a/qpdf/qtest/qpdf/enable-kfo.out b/qpdf/qtest/qpdf/enable-kfo.out index e49e7a1..64a38ef 100644 --- a/qpdf/qtest/qpdf/enable-kfo.out +++ b/qpdf/qtest/qpdf/enable-kfo.out @@ -1,34 +1,34 @@ -qpdf: selecting --keep-open-files=y -qpdf: processing 010-kfo.pdf -qpdf: processing 011-kfo.pdf -qpdf: processing 012-kfo.pdf -qpdf: processing 013-kfo.pdf -qpdf: processing 014-kfo.pdf -qpdf: processing 015-kfo.pdf -qpdf: processing 016-kfo.pdf -qpdf: processing 017-kfo.pdf -qpdf: processing 018-kfo.pdf -qpdf: processing 019-kfo.pdf qpdf: empty PDF: checking for shared resources qpdf: no shared resources found +qpdf: selecting --keep-open-files=y +qpdf: processing 010-kfo.pdf qpdf: 010-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 011-kfo.pdf qpdf: 011-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 012-kfo.pdf qpdf: 012-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 013-kfo.pdf qpdf: 013-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 014-kfo.pdf qpdf: 014-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 015-kfo.pdf qpdf: 015-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 016-kfo.pdf qpdf: 016-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 017-kfo.pdf qpdf: 017-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 018-kfo.pdf qpdf: 018-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 019-kfo.pdf qpdf: 019-kfo.pdf: checking for shared resources qpdf: no shared resources found qpdf: removing unreferenced pages from primary input diff --git a/qpdf/qtest/qpdf/kfo-n.out b/qpdf/qtest/qpdf/kfo-n.out index 1964bcd..f295aee 100644 --- a/qpdf/qtest/qpdf/kfo-n.out +++ b/qpdf/qtest/qpdf/kfo-n.out @@ -1,30 +1,30 @@ -qpdf: processing 001-kfo.pdf -qpdf: processing 002-kfo.pdf -qpdf: processing 003-kfo.pdf -qpdf: processing 004-kfo.pdf -qpdf: processing 005-kfo.pdf -qpdf: processing 006-kfo.pdf -qpdf: processing 007-kfo.pdf -qpdf: processing 008-kfo.pdf -qpdf: processing 009-kfo.pdf qpdf: empty PDF: checking for shared resources qpdf: no shared resources found +qpdf: processing 001-kfo.pdf qpdf: 001-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 002-kfo.pdf qpdf: 002-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 003-kfo.pdf qpdf: 003-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 004-kfo.pdf qpdf: 004-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 005-kfo.pdf qpdf: 005-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 006-kfo.pdf qpdf: 006-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 007-kfo.pdf qpdf: 007-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 008-kfo.pdf qpdf: 008-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 009-kfo.pdf qpdf: 009-kfo.pdf: checking for shared resources qpdf: no shared resources found qpdf: removing unreferenced pages from primary input diff --git a/qpdf/qtest/qpdf/kfo-y.out b/qpdf/qtest/qpdf/kfo-y.out index 1964bcd..f295aee 100644 --- a/qpdf/qtest/qpdf/kfo-y.out +++ b/qpdf/qtest/qpdf/kfo-y.out @@ -1,30 +1,30 @@ -qpdf: processing 001-kfo.pdf -qpdf: processing 002-kfo.pdf -qpdf: processing 003-kfo.pdf -qpdf: processing 004-kfo.pdf -qpdf: processing 005-kfo.pdf -qpdf: processing 006-kfo.pdf -qpdf: processing 007-kfo.pdf -qpdf: processing 008-kfo.pdf -qpdf: processing 009-kfo.pdf qpdf: empty PDF: checking for shared resources qpdf: no shared resources found +qpdf: processing 001-kfo.pdf qpdf: 001-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 002-kfo.pdf qpdf: 002-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 003-kfo.pdf qpdf: 003-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 004-kfo.pdf qpdf: 004-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 005-kfo.pdf qpdf: 005-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 006-kfo.pdf qpdf: 006-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 007-kfo.pdf qpdf: 007-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 008-kfo.pdf qpdf: 008-kfo.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 009-kfo.pdf qpdf: 009-kfo.pdf: checking for shared resources qpdf: no shared resources found qpdf: removing unreferenced pages from primary input diff --git a/qpdf/qtest/qpdf/uo-6.out b/qpdf/qtest/qpdf/uo-6.out index d11b034..3319594 100644 --- a/qpdf/qtest/qpdf/uo-6.out +++ b/qpdf/qtest/qpdf/uo-6.out @@ -1,6 +1,6 @@ -qpdf: selecting --keep-open-files=y qpdf: fxo-red.pdf: checking for shared resources qpdf: no shared resources found +qpdf: selecting --keep-open-files=y qpdf: removing unreferenced pages from primary input qpdf: adding pages from fxo-red.pdf qpdf: processing underlay/overlay diff --git a/qpdf/qtest/qpdf/uo-8.out b/qpdf/qtest/qpdf/uo-8.out index a2d67f8..b34a573 100644 --- a/qpdf/qtest/qpdf/uo-8.out +++ b/qpdf/qtest/qpdf/uo-8.out @@ -1,6 +1,6 @@ -qpdf: selecting --keep-open-files=y qpdf: fxo-red.pdf: checking for shared resources qpdf: no shared resources found +qpdf: selecting --keep-open-files=y qpdf: removing unreferenced pages from primary input qpdf: adding pages from fxo-red.pdf qpdf: processing underlay/overlay diff --git a/qpdf/qtest/qpdf/verbose-merge.out b/qpdf/qtest/qpdf/verbose-merge.out index 6f9846f..9222230 100644 --- a/qpdf/qtest/qpdf/verbose-merge.out +++ b/qpdf/qtest/qpdf/verbose-merge.out @@ -1,15 +1,15 @@ +qpdf: page-labels-and-outlines.pdf: checking for shared resources +qpdf: no shared resources found qpdf: selecting --keep-open-files=y -qpdf: processing 20-pages.pdf qpdf: processing ./20-pages.pdf -qpdf: processing minimal.pdf qpdf: ./20-pages.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing 20-pages.pdf qpdf: 20-pages.pdf: checking for shared resources qpdf: no shared resources found +qpdf: processing minimal.pdf qpdf: minimal.pdf: checking for shared resources qpdf: no shared resources found -qpdf: page-labels-and-outlines.pdf: checking for shared resources -qpdf: no shared resources found qpdf: removing unreferenced pages from primary input qpdf: adding pages from page-labels-and-outlines.pdf qpdf: adding pages from 20-pages.pdf