diff --git a/examples/examples.testcov b/examples/examples.testcov index 5a19b7c..e0811a1 100644 --- a/examples/examples.testcov +++ b/examples/examples.testcov @@ -1,12 +1,3 @@ -pdf-bookmarks lines 0 -pdf-bookmarks numbers 0 -pdf-bookmarks none 0 -pdf-bookmarks has count 0 -pdf-bookmarks no count 0 -pdf-bookmarks open 0 -pdf-bookmarks closed 0 -pdf-bookmarks dest 0 -pdf-bookmarks targets 0 pdf-mod-info --dump 0 pdf-mod-info no in file 0 pdf-mod-info in-place 0 diff --git a/examples/pdf-bookmarks.cc b/examples/pdf-bookmarks.cc index dc0c278..0aaf564 100644 --- a/examples/pdf-bookmarks.cc +++ b/examples/pdf-bookmarks.cc @@ -47,7 +47,7 @@ print_lines(std::vector& numbers) void generate_page_map(QPDF& qpdf) { - QPDFPageDocumentHelper dh(qpdf); + auto& dh = QPDFPageDocumentHelper::get(qpdf); int n = 0; for (auto const& page: dh.getAllPages()) { page_map[page] = ++n; @@ -60,11 +60,9 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector numbers) // No default so gcc will warn on missing tag switch (style) { case st_none: - QTC::TC("examples", "pdf-bookmarks none"); break; case st_numbers: - QTC::TC("examples", "pdf-bookmarks numbers"); for (auto const& number: numbers) { std::cout << number << "."; } @@ -72,7 +70,6 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector numbers) break; case st_lines: - QTC::TC("examples", "pdf-bookmarks lines"); print_lines(numbers); std::cout << "|\n"; print_lines(numbers); @@ -83,27 +80,21 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector numbers) if (show_open) { int count = outline.getCount(); if (count) { - QTC::TC("examples", "pdf-bookmarks has count"); if (count > 0) { // hierarchy is open at this point - QTC::TC("examples", "pdf-bookmarks open"); std::cout << "(v) "; } else { - QTC::TC("examples", "pdf-bookmarks closed"); std::cout << "(>) "; } } else { - QTC::TC("examples", "pdf-bookmarks no count"); std::cout << "( ) "; } } if (show_targets) { - QTC::TC("examples", "pdf-bookmarks targets"); std::string target = "unknown"; QPDFObjectHandle dest_page = outline.getDestPage(); if (!dest_page.isNull()) { - QTC::TC("examples", "pdf-bookmarks dest"); if (page_map.contains(dest_page)) { target = std::to_string(page_map[dest_page]); } diff --git a/examples/pdf-count-strings.cc b/examples/pdf-count-strings.cc index c262459..1a6ad7e 100644 --- a/examples/pdf-count-strings.cc +++ b/examples/pdf-count-strings.cc @@ -76,7 +76,7 @@ main(int argc, char* argv[]) QPDF pdf; pdf.processFile(infilename); int pageno = 0; - for (auto& page: QPDFPageDocumentHelper(pdf).getAllPages()) { + for (auto& page: QPDFPageDocumentHelper::get(pdf).getAllPages()) { ++pageno; // Pass the contents of a page through our string counter. If it's an even page, capture // the output. This illustrates that you may capture any output generated by the filter, diff --git a/examples/pdf-create.cc b/examples/pdf-create.cc index f09d8c4..070bc5f 100644 --- a/examples/pdf-create.cc +++ b/examples/pdf-create.cc @@ -229,7 +229,7 @@ check( QPDF pdf; pdf.processFile(filename); - auto pages = QPDFPageDocumentHelper(pdf).getAllPages(); + auto pages = QPDFPageDocumentHelper::get(pdf).getAllPages(); if (n_color_spaces * n_filters != pages.size()) { throw std::logic_error("incorrect number of pages"); } diff --git a/examples/pdf-overlay-page.cc b/examples/pdf-overlay-page.cc index d4da647..fc9dd79 100644 --- a/examples/pdf-overlay-page.cc +++ b/examples/pdf-overlay-page.cc @@ -29,14 +29,14 @@ stamp_page(char const* infile, char const* stampfile, char const* outfile) stamppdf.processFile(stampfile); // Get first page from other file - QPDFPageObjectHelper stamp_page_1 = QPDFPageDocumentHelper(stamppdf).getAllPages().at(0); + QPDFPageObjectHelper stamp_page_1 = QPDFPageDocumentHelper::get(stamppdf).getAllPages().at(0); // Convert page to a form XObject QPDFObjectHandle foreign_fo = stamp_page_1.getFormXObjectForPage(); // Copy form XObject to the input file QPDFObjectHandle stamp_fo = inpdf.copyForeignObject(foreign_fo); // For each page... - for (auto& ph: QPDFPageDocumentHelper(inpdf).getAllPages()) { + for (auto& ph: QPDFPageDocumentHelper::get(inpdf).getAllPages()) { // Find a unique resource name for the new form XObject QPDFObjectHandle resources = ph.getAttribute("/Resources", true); int min_suffix = 1; diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index b8b5e16..23f910e 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -65,6 +65,7 @@ class QPDFParser; class QPDFAcroFormDocumentHelper; class QPDFEmbeddedFileDocumentHelper; class QPDFOutlineDocumentHelper; +class QPDFPageDocumentHelper; class QPDFPageLabelDocumentHelper; class QPDF @@ -804,6 +805,7 @@ class QPDF inline QPDFAcroFormDocumentHelper& acroform(); inline QPDFEmbeddedFileDocumentHelper& embedded_files(); inline QPDFOutlineDocumentHelper& outlines(); + inline QPDFPageDocumentHelper& pages(); inline QPDFPageLabelDocumentHelper& page_labels(); // For testing only -- do not add to DLL diff --git a/include/qpdf/QPDFPageDocumentHelper.hh b/include/qpdf/QPDFPageDocumentHelper.hh index bad7e68..3714258 100644 --- a/include/qpdf/QPDFPageDocumentHelper.hh +++ b/include/qpdf/QPDFPageDocumentHelper.hh @@ -35,6 +35,21 @@ class QPDFAcroFormDocumentHelper; class QPDFPageDocumentHelper: public QPDFDocumentHelper { public: + // Get a shared document helper for a given QPDF object. + // + // Retrieving a document helper for a QPDF object rather than creating a new one avoids repeated + // validation of the Acroform structure, which can be expensive. + QPDF_DLL + static QPDFPageDocumentHelper& get(QPDF& qpdf); + + // Re-validate the Pages structure. This is useful if you have modified the Pages structure in + // a way that would invalidate the cache. + // + // If repair is true, the document will be repaired if possible if the validation encounters + // errors. + QPDF_DLL + void validate(bool repair = true); + QPDF_DLL QPDFPageDocumentHelper(QPDF&); @@ -112,17 +127,7 @@ class QPDFPageDocumentHelper: public QPDFDocumentHelper int required_flags, int forbidden_flags); - class Members - { - friend class QPDFPageDocumentHelper; - - public: - ~Members() = default; - - private: - Members() = default; - Members(Members const&) = delete; - }; + class Members; std::shared_ptr m; }; diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index 33a2c3e..01b333b 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -790,7 +789,7 @@ QPDFJob::doCheck(QPDF& pdf) // Parse all content streams int pageno = 0; - for (auto& page: QPDFPageDocumentHelper(pdf).getAllPages()) { + for (auto& page: pdf.pages().getAllPages()) { ++pageno; try { page.parseContents(nullptr); @@ -858,7 +857,7 @@ QPDFJob::doShowPages(QPDF& pdf) { int pageno = 0; auto& cout = *m->log->getInfo(); - for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { + for (auto& ph: pdf.pages().getAllPages()) { QPDFObjectHandle page = ph.getObjectHandle(); ++pageno; @@ -1052,7 +1051,7 @@ QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf) auto& pldh = pdf.page_labels(); auto& odh = pdf.outlines(); int pageno = -1; - for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { + for (auto& ph: pdf.pages().getAllPages()) { ++pageno; JSON j_page = JSON::makeDictionary(); QPDFObjectHandle page = ph.getObjectHandle(); @@ -1113,7 +1112,7 @@ QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf) { JSON j_labels = JSON::makeArray(); auto& pldh = pdf.page_labels(); - long long npages = QIntC::to_longlong(QPDFPageDocumentHelper(pdf).getAllPages().size()); + long long npages = QIntC::to_longlong(pdf.pages().getAllPages().size()); if (pldh.hasPageLabels()) { std::vector labels; pldh.getLabelsForPageRange(0, npages - 1, 0, labels); @@ -1161,7 +1160,7 @@ QPDFJob::doJSONOutlines(Pipeline* p, bool& first, QPDF& pdf) { std::map page_numbers; int n = 0; - for (auto const& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { + for (auto const& ph: pdf.pages().getAllPages()) { QPDFObjectHandle oh = ph.getObjectHandle(); page_numbers[oh.getObjGen()] = ++n; } @@ -1180,7 +1179,7 @@ QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf) j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances())); JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray()); int pagepos1 = 0; - for (auto const& page: QPDFPageDocumentHelper(pdf).getAllPages()) { + for (auto const& page: pdf.pages().getAllPages()) { ++pagepos1; for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) { QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh); @@ -1857,7 +1856,7 @@ QPDFJob::processInputSource( void QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) { - QPDFPageDocumentHelper main_pdh(pdf); + 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)); diff --git a/libqpdf/QPDFPageDocumentHelper.cc b/libqpdf/QPDFPageDocumentHelper.cc index 154443e..98ef166 100644 --- a/libqpdf/QPDFPageDocumentHelper.cc +++ b/libqpdf/QPDFPageDocumentHelper.cc @@ -6,11 +6,26 @@ #include #include +class QPDFPageDocumentHelper::Members +{ +}; + QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) : QPDFDocumentHelper(qpdf) { } +QPDFPageDocumentHelper& +QPDFPageDocumentHelper::get(QPDF& qpdf) +{ + return qpdf.pages(); +} + +void +QPDFPageDocumentHelper::validate(bool repair) +{ +} + std::vector QPDFPageDocumentHelper::getAllPages() { diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index 2f22b7f..e0fc568 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -558,6 +559,7 @@ class QPDF::Members std::unique_ptr acroform; std::unique_ptr embedded_files; std::unique_ptr outlines; + std::unique_ptr pages; std::unique_ptr page_labels; }; @@ -608,6 +610,15 @@ QPDF::outlines() return *m->outlines; } +inline QPDFPageDocumentHelper& +QPDF::pages() +{ + if (!m->pages) { + m->pages = std::make_unique(*this); + } + return *m->pages; +} + inline QPDFPageLabelDocumentHelper& QPDF::page_labels() { diff --git a/manual/release-notes.rst b/manual/release-notes.rst index 82c43be..7a03a90 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -26,20 +26,27 @@ more detail. - Library Enhancements - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` - constructor overloads that allow a function to ne passed to + constructor overloads that allow a function to be passed to validate the values in the tree. - Add new ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` ``validate`` method to validate and optionally repair the name/number tree. + - Add new ``get`` and ``validate`` methods to all DocumentHelper classes. + The ``get`` method retrieves a shared DocumentHelper, avoiding the the + overhead of repeatedly validating the underlying document structure + and/or building internal caches. If the underlying document structure + is directly modified (without the use of DocumentHelpers), the + ``validate`` methods revalidates the structure and resynchronizes any + internal caches. + - CLI Enhancements - Disallow option :qpdf:ref:`--deterministic-id` to be used together with the incompatible options :qpdf:ref:`--encrypt` or :qpdf:ref:`--copy-encryption`. - - Other enhancements - ``QPDFWriter`` will no longer add filters when writing empty streams.