Commit 36e1c1425f124170d6af907312bdc9c744805654
1 parent
847f0246
Integrate `QPDFPageDocumentHelper` with `QPDF` for improved page management. Add…
… shared helper retrieval, validation methods, and update usages across the codebase. Remove unused test coverage entries.
Showing
11 changed files
with
65 additions
and
44 deletions
examples/examples.testcov
| 1 | -pdf-bookmarks lines 0 | |
| 2 | -pdf-bookmarks numbers 0 | |
| 3 | -pdf-bookmarks none 0 | |
| 4 | -pdf-bookmarks has count 0 | |
| 5 | -pdf-bookmarks no count 0 | |
| 6 | -pdf-bookmarks open 0 | |
| 7 | -pdf-bookmarks closed 0 | |
| 8 | -pdf-bookmarks dest 0 | |
| 9 | -pdf-bookmarks targets 0 | |
| 10 | 1 | pdf-mod-info --dump 0 |
| 11 | 2 | pdf-mod-info no in file 0 |
| 12 | 3 | pdf-mod-info in-place 0 | ... | ... |
examples/pdf-bookmarks.cc
| ... | ... | @@ -47,7 +47,7 @@ print_lines(std::vector<int>& numbers) |
| 47 | 47 | void |
| 48 | 48 | generate_page_map(QPDF& qpdf) |
| 49 | 49 | { |
| 50 | - QPDFPageDocumentHelper dh(qpdf); | |
| 50 | + auto& dh = QPDFPageDocumentHelper::get(qpdf); | |
| 51 | 51 | int n = 0; |
| 52 | 52 | for (auto const& page: dh.getAllPages()) { |
| 53 | 53 | page_map[page] = ++n; |
| ... | ... | @@ -60,11 +60,9 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) |
| 60 | 60 | // No default so gcc will warn on missing tag |
| 61 | 61 | switch (style) { |
| 62 | 62 | case st_none: |
| 63 | - QTC::TC("examples", "pdf-bookmarks none"); | |
| 64 | 63 | break; |
| 65 | 64 | |
| 66 | 65 | case st_numbers: |
| 67 | - QTC::TC("examples", "pdf-bookmarks numbers"); | |
| 68 | 66 | for (auto const& number: numbers) { |
| 69 | 67 | std::cout << number << "."; |
| 70 | 68 | } |
| ... | ... | @@ -72,7 +70,6 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) |
| 72 | 70 | break; |
| 73 | 71 | |
| 74 | 72 | case st_lines: |
| 75 | - QTC::TC("examples", "pdf-bookmarks lines"); | |
| 76 | 73 | print_lines(numbers); |
| 77 | 74 | std::cout << "|\n"; |
| 78 | 75 | print_lines(numbers); |
| ... | ... | @@ -83,27 +80,21 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) |
| 83 | 80 | if (show_open) { |
| 84 | 81 | int count = outline.getCount(); |
| 85 | 82 | if (count) { |
| 86 | - QTC::TC("examples", "pdf-bookmarks has count"); | |
| 87 | 83 | if (count > 0) { |
| 88 | 84 | // hierarchy is open at this point |
| 89 | - QTC::TC("examples", "pdf-bookmarks open"); | |
| 90 | 85 | std::cout << "(v) "; |
| 91 | 86 | } else { |
| 92 | - QTC::TC("examples", "pdf-bookmarks closed"); | |
| 93 | 87 | std::cout << "(>) "; |
| 94 | 88 | } |
| 95 | 89 | } else { |
| 96 | - QTC::TC("examples", "pdf-bookmarks no count"); | |
| 97 | 90 | std::cout << "( ) "; |
| 98 | 91 | } |
| 99 | 92 | } |
| 100 | 93 | |
| 101 | 94 | if (show_targets) { |
| 102 | - QTC::TC("examples", "pdf-bookmarks targets"); | |
| 103 | 95 | std::string target = "unknown"; |
| 104 | 96 | QPDFObjectHandle dest_page = outline.getDestPage(); |
| 105 | 97 | if (!dest_page.isNull()) { |
| 106 | - QTC::TC("examples", "pdf-bookmarks dest"); | |
| 107 | 98 | if (page_map.contains(dest_page)) { |
| 108 | 99 | target = std::to_string(page_map[dest_page]); |
| 109 | 100 | } | ... | ... |
examples/pdf-count-strings.cc
| ... | ... | @@ -76,7 +76,7 @@ main(int argc, char* argv[]) |
| 76 | 76 | QPDF pdf; |
| 77 | 77 | pdf.processFile(infilename); |
| 78 | 78 | int pageno = 0; |
| 79 | - for (auto& page: QPDFPageDocumentHelper(pdf).getAllPages()) { | |
| 79 | + for (auto& page: QPDFPageDocumentHelper::get(pdf).getAllPages()) { | |
| 80 | 80 | ++pageno; |
| 81 | 81 | // Pass the contents of a page through our string counter. If it's an even page, capture |
| 82 | 82 | // the output. This illustrates that you may capture any output generated by the filter, | ... | ... |
examples/pdf-create.cc
| ... | ... | @@ -229,7 +229,7 @@ check( |
| 229 | 229 | |
| 230 | 230 | QPDF pdf; |
| 231 | 231 | pdf.processFile(filename); |
| 232 | - auto pages = QPDFPageDocumentHelper(pdf).getAllPages(); | |
| 232 | + auto pages = QPDFPageDocumentHelper::get(pdf).getAllPages(); | |
| 233 | 233 | if (n_color_spaces * n_filters != pages.size()) { |
| 234 | 234 | throw std::logic_error("incorrect number of pages"); |
| 235 | 235 | } | ... | ... |
examples/pdf-overlay-page.cc
| ... | ... | @@ -29,14 +29,14 @@ stamp_page(char const* infile, char const* stampfile, char const* outfile) |
| 29 | 29 | stamppdf.processFile(stampfile); |
| 30 | 30 | |
| 31 | 31 | // Get first page from other file |
| 32 | - QPDFPageObjectHelper stamp_page_1 = QPDFPageDocumentHelper(stamppdf).getAllPages().at(0); | |
| 32 | + QPDFPageObjectHelper stamp_page_1 = QPDFPageDocumentHelper::get(stamppdf).getAllPages().at(0); | |
| 33 | 33 | // Convert page to a form XObject |
| 34 | 34 | QPDFObjectHandle foreign_fo = stamp_page_1.getFormXObjectForPage(); |
| 35 | 35 | // Copy form XObject to the input file |
| 36 | 36 | QPDFObjectHandle stamp_fo = inpdf.copyForeignObject(foreign_fo); |
| 37 | 37 | |
| 38 | 38 | // For each page... |
| 39 | - for (auto& ph: QPDFPageDocumentHelper(inpdf).getAllPages()) { | |
| 39 | + for (auto& ph: QPDFPageDocumentHelper::get(inpdf).getAllPages()) { | |
| 40 | 40 | // Find a unique resource name for the new form XObject |
| 41 | 41 | QPDFObjectHandle resources = ph.getAttribute("/Resources", true); |
| 42 | 42 | int min_suffix = 1; | ... | ... |
include/qpdf/QPDF.hh
| ... | ... | @@ -65,6 +65,7 @@ class QPDFParser; |
| 65 | 65 | class QPDFAcroFormDocumentHelper; |
| 66 | 66 | class QPDFEmbeddedFileDocumentHelper; |
| 67 | 67 | class QPDFOutlineDocumentHelper; |
| 68 | +class QPDFPageDocumentHelper; | |
| 68 | 69 | class QPDFPageLabelDocumentHelper; |
| 69 | 70 | |
| 70 | 71 | class QPDF |
| ... | ... | @@ -804,6 +805,7 @@ class QPDF |
| 804 | 805 | inline QPDFAcroFormDocumentHelper& acroform(); |
| 805 | 806 | inline QPDFEmbeddedFileDocumentHelper& embedded_files(); |
| 806 | 807 | inline QPDFOutlineDocumentHelper& outlines(); |
| 808 | + inline QPDFPageDocumentHelper& pages(); | |
| 807 | 809 | inline QPDFPageLabelDocumentHelper& page_labels(); |
| 808 | 810 | |
| 809 | 811 | // For testing only -- do not add to DLL | ... | ... |
include/qpdf/QPDFPageDocumentHelper.hh
| ... | ... | @@ -35,6 +35,21 @@ class QPDFAcroFormDocumentHelper; |
| 35 | 35 | class QPDFPageDocumentHelper: public QPDFDocumentHelper |
| 36 | 36 | { |
| 37 | 37 | public: |
| 38 | + // Get a shared document helper for a given QPDF object. | |
| 39 | + // | |
| 40 | + // Retrieving a document helper for a QPDF object rather than creating a new one avoids repeated | |
| 41 | + // validation of the Acroform structure, which can be expensive. | |
| 42 | + QPDF_DLL | |
| 43 | + static QPDFPageDocumentHelper& get(QPDF& qpdf); | |
| 44 | + | |
| 45 | + // Re-validate the Pages structure. This is useful if you have modified the Pages structure in | |
| 46 | + // a way that would invalidate the cache. | |
| 47 | + // | |
| 48 | + // If repair is true, the document will be repaired if possible if the validation encounters | |
| 49 | + // errors. | |
| 50 | + QPDF_DLL | |
| 51 | + void validate(bool repair = true); | |
| 52 | + | |
| 38 | 53 | QPDF_DLL |
| 39 | 54 | QPDFPageDocumentHelper(QPDF&); |
| 40 | 55 | |
| ... | ... | @@ -112,17 +127,7 @@ class QPDFPageDocumentHelper: public QPDFDocumentHelper |
| 112 | 127 | int required_flags, |
| 113 | 128 | int forbidden_flags); |
| 114 | 129 | |
| 115 | - class Members | |
| 116 | - { | |
| 117 | - friend class QPDFPageDocumentHelper; | |
| 118 | - | |
| 119 | - public: | |
| 120 | - ~Members() = default; | |
| 121 | - | |
| 122 | - private: | |
| 123 | - Members() = default; | |
| 124 | - Members(Members const&) = delete; | |
| 125 | - }; | |
| 130 | + class Members; | |
| 126 | 131 | |
| 127 | 132 | std::shared_ptr<Members> m; |
| 128 | 133 | }; | ... | ... |
libqpdf/QPDFJob.cc
| ... | ... | @@ -17,7 +17,6 @@ |
| 17 | 17 | #include <qpdf/QPDFExc.hh> |
| 18 | 18 | #include <qpdf/QPDFLogger.hh> |
| 19 | 19 | #include <qpdf/QPDFObjectHandle_private.hh> |
| 20 | -#include <qpdf/QPDFPageDocumentHelper.hh> | |
| 21 | 20 | #include <qpdf/QPDFPageObjectHelper.hh> |
| 22 | 21 | #include <qpdf/QPDFSystemError.hh> |
| 23 | 22 | #include <qpdf/QPDFUsage.hh> |
| ... | ... | @@ -790,7 +789,7 @@ QPDFJob::doCheck(QPDF& pdf) |
| 790 | 789 | |
| 791 | 790 | // Parse all content streams |
| 792 | 791 | int pageno = 0; |
| 793 | - for (auto& page: QPDFPageDocumentHelper(pdf).getAllPages()) { | |
| 792 | + for (auto& page: pdf.pages().getAllPages()) { | |
| 794 | 793 | ++pageno; |
| 795 | 794 | try { |
| 796 | 795 | page.parseContents(nullptr); |
| ... | ... | @@ -858,7 +857,7 @@ QPDFJob::doShowPages(QPDF& pdf) |
| 858 | 857 | { |
| 859 | 858 | int pageno = 0; |
| 860 | 859 | auto& cout = *m->log->getInfo(); |
| 861 | - for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { | |
| 860 | + for (auto& ph: pdf.pages().getAllPages()) { | |
| 862 | 861 | QPDFObjectHandle page = ph.getObjectHandle(); |
| 863 | 862 | ++pageno; |
| 864 | 863 | |
| ... | ... | @@ -1052,7 +1051,7 @@ QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf) |
| 1052 | 1051 | auto& pldh = pdf.page_labels(); |
| 1053 | 1052 | auto& odh = pdf.outlines(); |
| 1054 | 1053 | int pageno = -1; |
| 1055 | - for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { | |
| 1054 | + for (auto& ph: pdf.pages().getAllPages()) { | |
| 1056 | 1055 | ++pageno; |
| 1057 | 1056 | JSON j_page = JSON::makeDictionary(); |
| 1058 | 1057 | QPDFObjectHandle page = ph.getObjectHandle(); |
| ... | ... | @@ -1113,7 +1112,7 @@ QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf) |
| 1113 | 1112 | { |
| 1114 | 1113 | JSON j_labels = JSON::makeArray(); |
| 1115 | 1114 | auto& pldh = pdf.page_labels(); |
| 1116 | - long long npages = QIntC::to_longlong(QPDFPageDocumentHelper(pdf).getAllPages().size()); | |
| 1115 | + long long npages = QIntC::to_longlong(pdf.pages().getAllPages().size()); | |
| 1117 | 1116 | if (pldh.hasPageLabels()) { |
| 1118 | 1117 | std::vector<QPDFObjectHandle> labels; |
| 1119 | 1118 | pldh.getLabelsForPageRange(0, npages - 1, 0, labels); |
| ... | ... | @@ -1161,7 +1160,7 @@ QPDFJob::doJSONOutlines(Pipeline* p, bool& first, QPDF& pdf) |
| 1161 | 1160 | { |
| 1162 | 1161 | std::map<QPDFObjGen, int> page_numbers; |
| 1163 | 1162 | int n = 0; |
| 1164 | - for (auto const& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { | |
| 1163 | + for (auto const& ph: pdf.pages().getAllPages()) { | |
| 1165 | 1164 | QPDFObjectHandle oh = ph.getObjectHandle(); |
| 1166 | 1165 | page_numbers[oh.getObjGen()] = ++n; |
| 1167 | 1166 | } |
| ... | ... | @@ -1180,7 +1179,7 @@ QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf) |
| 1180 | 1179 | j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances())); |
| 1181 | 1180 | JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray()); |
| 1182 | 1181 | int pagepos1 = 0; |
| 1183 | - for (auto const& page: QPDFPageDocumentHelper(pdf).getAllPages()) { | |
| 1182 | + for (auto const& page: pdf.pages().getAllPages()) { | |
| 1184 | 1183 | ++pagepos1; |
| 1185 | 1184 | for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) { |
| 1186 | 1185 | QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh); |
| ... | ... | @@ -1857,7 +1856,7 @@ QPDFJob::processInputSource( |
| 1857 | 1856 | void |
| 1858 | 1857 | QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) |
| 1859 | 1858 | { |
| 1860 | - QPDFPageDocumentHelper main_pdh(pdf); | |
| 1859 | + auto& main_pdh = pdf.pages(); | |
| 1861 | 1860 | int main_npages = QIntC::to_int(main_pdh.getAllPages().size()); |
| 1862 | 1861 | processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false); |
| 1863 | 1862 | QPDFPageDocumentHelper uo_pdh(*(uo->pdf)); | ... | ... |
libqpdf/QPDFPageDocumentHelper.cc
| ... | ... | @@ -6,11 +6,26 @@ |
| 6 | 6 | #include <qpdf/QTC.hh> |
| 7 | 7 | #include <qpdf/QUtil.hh> |
| 8 | 8 | |
| 9 | +class QPDFPageDocumentHelper::Members | |
| 10 | +{ | |
| 11 | +}; | |
| 12 | + | |
| 9 | 13 | QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) : |
| 10 | 14 | QPDFDocumentHelper(qpdf) |
| 11 | 15 | { |
| 12 | 16 | } |
| 13 | 17 | |
| 18 | +QPDFPageDocumentHelper& | |
| 19 | +QPDFPageDocumentHelper::get(QPDF& qpdf) | |
| 20 | +{ | |
| 21 | + return qpdf.pages(); | |
| 22 | +} | |
| 23 | + | |
| 24 | +void | |
| 25 | +QPDFPageDocumentHelper::validate(bool repair) | |
| 26 | +{ | |
| 27 | +} | |
| 28 | + | |
| 14 | 29 | std::vector<QPDFPageObjectHelper> |
| 15 | 30 | QPDFPageDocumentHelper::getAllPages() |
| 16 | 31 | { | ... | ... |
libqpdf/qpdf/QPDF_private.hh
| ... | ... | @@ -7,6 +7,7 @@ |
| 7 | 7 | #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> |
| 8 | 8 | #include <qpdf/QPDFObject_private.hh> |
| 9 | 9 | #include <qpdf/QPDFOutlineDocumentHelper.hh> |
| 10 | +#include <qpdf/QPDFPageDocumentHelper.hh> | |
| 10 | 11 | #include <qpdf/QPDFPageLabelDocumentHelper.hh> |
| 11 | 12 | #include <qpdf/QPDFTokenizer_private.hh> |
| 12 | 13 | |
| ... | ... | @@ -558,6 +559,7 @@ class QPDF::Members |
| 558 | 559 | std::unique_ptr<QPDFAcroFormDocumentHelper> acroform; |
| 559 | 560 | std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files; |
| 560 | 561 | std::unique_ptr<QPDFOutlineDocumentHelper> outlines; |
| 562 | + std::unique_ptr<QPDFPageDocumentHelper> pages; | |
| 561 | 563 | std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels; |
| 562 | 564 | }; |
| 563 | 565 | |
| ... | ... | @@ -608,6 +610,15 @@ QPDF::outlines() |
| 608 | 610 | return *m->outlines; |
| 609 | 611 | } |
| 610 | 612 | |
| 613 | +inline QPDFPageDocumentHelper& | |
| 614 | +QPDF::pages() | |
| 615 | +{ | |
| 616 | + if (!m->pages) { | |
| 617 | + m->pages = std::make_unique<QPDFPageDocumentHelper>(*this); | |
| 618 | + } | |
| 619 | + return *m->pages; | |
| 620 | +} | |
| 621 | + | |
| 611 | 622 | inline QPDFPageLabelDocumentHelper& |
| 612 | 623 | QPDF::page_labels() |
| 613 | 624 | { | ... | ... |
manual/release-notes.rst
| ... | ... | @@ -26,20 +26,27 @@ more detail. |
| 26 | 26 | - Library Enhancements |
| 27 | 27 | |
| 28 | 28 | - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` |
| 29 | - constructor overloads that allow a function to ne passed to | |
| 29 | + constructor overloads that allow a function to be passed to | |
| 30 | 30 | validate the values in the tree. |
| 31 | 31 | |
| 32 | 32 | - Add new ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` |
| 33 | 33 | ``validate`` method to validate and optionally repair the name/number |
| 34 | 34 | tree. |
| 35 | 35 | |
| 36 | + - Add new ``get`` and ``validate`` methods to all DocumentHelper classes. | |
| 37 | + The ``get`` method retrieves a shared DocumentHelper, avoiding the the | |
| 38 | + overhead of repeatedly validating the underlying document structure | |
| 39 | + and/or building internal caches. If the underlying document structure | |
| 40 | + is directly modified (without the use of DocumentHelpers), the | |
| 41 | + ``validate`` methods revalidates the structure and resynchronizes any | |
| 42 | + internal caches. | |
| 43 | + | |
| 36 | 44 | - CLI Enhancements |
| 37 | 45 | |
| 38 | 46 | - Disallow option :qpdf:ref:`--deterministic-id` to be used together |
| 39 | 47 | with the incompatible options :qpdf:ref:`--encrypt` or |
| 40 | 48 | :qpdf:ref:`--copy-encryption`. |
| 41 | 49 | |
| 42 | - | |
| 43 | 50 | - Other enhancements |
| 44 | 51 | |
| 45 | 52 | - ``QPDFWriter`` will no longer add filters when writing empty streams. | ... | ... |