Commit 6ccbc12148274e58c110873aaba629dbb6d5dc01
Committed by
GitHub
Merge pull request #1541 from m-holger/dh
Cache DocumentHelpers
Showing
26 changed files
with
272 additions
and
82 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 | pdf-mod-info --dump 0 | 1 | pdf-mod-info --dump 0 |
| 11 | pdf-mod-info no in file 0 | 2 | pdf-mod-info no in file 0 |
| 12 | pdf-mod-info in-place 0 | 3 | pdf-mod-info in-place 0 |
examples/pdf-attach-file.cc
| @@ -79,7 +79,7 @@ process( | @@ -79,7 +79,7 @@ process( | ||
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | // Add the embedded file at the document level as an attachment. | 81 | // Add the embedded file at the document level as an attachment. |
| 82 | - auto efdh = QPDFEmbeddedFileDocumentHelper(q); | 82 | + auto& efdh = QPDFEmbeddedFileDocumentHelper::get(q); |
| 83 | efdh.replaceEmbeddedFile(key, fs); | 83 | efdh.replaceEmbeddedFile(key, fs); |
| 84 | 84 | ||
| 85 | // Create a file attachment annotation. | 85 | // Create a file attachment annotation. |
examples/pdf-bookmarks.cc
| @@ -47,7 +47,7 @@ print_lines(std::vector<int>& numbers) | @@ -47,7 +47,7 @@ print_lines(std::vector<int>& numbers) | ||
| 47 | void | 47 | void |
| 48 | generate_page_map(QPDF& qpdf) | 48 | generate_page_map(QPDF& qpdf) |
| 49 | { | 49 | { |
| 50 | - QPDFPageDocumentHelper dh(qpdf); | 50 | + auto& dh = QPDFPageDocumentHelper::get(qpdf); |
| 51 | int n = 0; | 51 | int n = 0; |
| 52 | for (auto const& page: dh.getAllPages()) { | 52 | for (auto const& page: dh.getAllPages()) { |
| 53 | page_map[page] = ++n; | 53 | page_map[page] = ++n; |
| @@ -60,11 +60,9 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) | @@ -60,11 +60,9 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) | ||
| 60 | // No default so gcc will warn on missing tag | 60 | // No default so gcc will warn on missing tag |
| 61 | switch (style) { | 61 | switch (style) { |
| 62 | case st_none: | 62 | case st_none: |
| 63 | - QTC::TC("examples", "pdf-bookmarks none"); | ||
| 64 | break; | 63 | break; |
| 65 | 64 | ||
| 66 | case st_numbers: | 65 | case st_numbers: |
| 67 | - QTC::TC("examples", "pdf-bookmarks numbers"); | ||
| 68 | for (auto const& number: numbers) { | 66 | for (auto const& number: numbers) { |
| 69 | std::cout << number << "."; | 67 | std::cout << number << "."; |
| 70 | } | 68 | } |
| @@ -72,7 +70,6 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) | @@ -72,7 +70,6 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) | ||
| 72 | break; | 70 | break; |
| 73 | 71 | ||
| 74 | case st_lines: | 72 | case st_lines: |
| 75 | - QTC::TC("examples", "pdf-bookmarks lines"); | ||
| 76 | print_lines(numbers); | 73 | print_lines(numbers); |
| 77 | std::cout << "|\n"; | 74 | std::cout << "|\n"; |
| 78 | print_lines(numbers); | 75 | print_lines(numbers); |
| @@ -83,27 +80,21 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) | @@ -83,27 +80,21 @@ show_bookmark_details(QPDFOutlineObjectHelper outline, std::vector<int> numbers) | ||
| 83 | if (show_open) { | 80 | if (show_open) { |
| 84 | int count = outline.getCount(); | 81 | int count = outline.getCount(); |
| 85 | if (count) { | 82 | if (count) { |
| 86 | - QTC::TC("examples", "pdf-bookmarks has count"); | ||
| 87 | if (count > 0) { | 83 | if (count > 0) { |
| 88 | // hierarchy is open at this point | 84 | // hierarchy is open at this point |
| 89 | - QTC::TC("examples", "pdf-bookmarks open"); | ||
| 90 | std::cout << "(v) "; | 85 | std::cout << "(v) "; |
| 91 | } else { | 86 | } else { |
| 92 | - QTC::TC("examples", "pdf-bookmarks closed"); | ||
| 93 | std::cout << "(>) "; | 87 | std::cout << "(>) "; |
| 94 | } | 88 | } |
| 95 | } else { | 89 | } else { |
| 96 | - QTC::TC("examples", "pdf-bookmarks no count"); | ||
| 97 | std::cout << "( ) "; | 90 | std::cout << "( ) "; |
| 98 | } | 91 | } |
| 99 | } | 92 | } |
| 100 | 93 | ||
| 101 | if (show_targets) { | 94 | if (show_targets) { |
| 102 | - QTC::TC("examples", "pdf-bookmarks targets"); | ||
| 103 | std::string target = "unknown"; | 95 | std::string target = "unknown"; |
| 104 | QPDFObjectHandle dest_page = outline.getDestPage(); | 96 | QPDFObjectHandle dest_page = outline.getDestPage(); |
| 105 | if (!dest_page.isNull()) { | 97 | if (!dest_page.isNull()) { |
| 106 | - QTC::TC("examples", "pdf-bookmarks dest"); | ||
| 107 | if (page_map.contains(dest_page)) { | 98 | if (page_map.contains(dest_page)) { |
| 108 | target = std::to_string(page_map[dest_page]); | 99 | target = std::to_string(page_map[dest_page]); |
| 109 | } | 100 | } |
| @@ -177,7 +168,7 @@ main(int argc, char* argv[]) | @@ -177,7 +168,7 @@ main(int argc, char* argv[]) | ||
| 177 | QPDF qpdf; | 168 | QPDF qpdf; |
| 178 | qpdf.processFile(filename, password); | 169 | qpdf.processFile(filename, password); |
| 179 | 170 | ||
| 180 | - QPDFOutlineDocumentHelper odh(qpdf); | 171 | + auto& odh = QPDFOutlineDocumentHelper::get(qpdf); |
| 181 | if (odh.hasOutlines()) { | 172 | if (odh.hasOutlines()) { |
| 182 | std::vector<int> numbers; | 173 | std::vector<int> numbers; |
| 183 | if (show_targets) { | 174 | if (show_targets) { |
examples/pdf-count-strings.cc
| @@ -76,7 +76,7 @@ main(int argc, char* argv[]) | @@ -76,7 +76,7 @@ main(int argc, char* argv[]) | ||
| 76 | QPDF pdf; | 76 | QPDF pdf; |
| 77 | pdf.processFile(infilename); | 77 | pdf.processFile(infilename); |
| 78 | int pageno = 0; | 78 | int pageno = 0; |
| 79 | - for (auto& page: QPDFPageDocumentHelper(pdf).getAllPages()) { | 79 | + for (auto& page: QPDFPageDocumentHelper::get(pdf).getAllPages()) { |
| 80 | ++pageno; | 80 | ++pageno; |
| 81 | // Pass the contents of a page through our string counter. If it's an even page, capture | 81 | // Pass the contents of a page through our string counter. If it's an even page, capture |
| 82 | // the output. This illustrates that you may capture any output generated by the filter, | 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,7 +229,7 @@ check( | ||
| 229 | 229 | ||
| 230 | QPDF pdf; | 230 | QPDF pdf; |
| 231 | pdf.processFile(filename); | 231 | pdf.processFile(filename); |
| 232 | - auto pages = QPDFPageDocumentHelper(pdf).getAllPages(); | 232 | + auto pages = QPDFPageDocumentHelper::get(pdf).getAllPages(); |
| 233 | if (n_color_spaces * n_filters != pages.size()) { | 233 | if (n_color_spaces * n_filters != pages.size()) { |
| 234 | throw std::logic_error("incorrect number of pages"); | 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,14 +29,14 @@ stamp_page(char const* infile, char const* stampfile, char const* outfile) | ||
| 29 | stamppdf.processFile(stampfile); | 29 | stamppdf.processFile(stampfile); |
| 30 | 30 | ||
| 31 | // Get first page from other file | 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 | // Convert page to a form XObject | 33 | // Convert page to a form XObject |
| 34 | QPDFObjectHandle foreign_fo = stamp_page_1.getFormXObjectForPage(); | 34 | QPDFObjectHandle foreign_fo = stamp_page_1.getFormXObjectForPage(); |
| 35 | // Copy form XObject to the input file | 35 | // Copy form XObject to the input file |
| 36 | QPDFObjectHandle stamp_fo = inpdf.copyForeignObject(foreign_fo); | 36 | QPDFObjectHandle stamp_fo = inpdf.copyForeignObject(foreign_fo); |
| 37 | 37 | ||
| 38 | // For each page... | 38 | // For each page... |
| 39 | - for (auto& ph: QPDFPageDocumentHelper(inpdf).getAllPages()) { | 39 | + for (auto& ph: QPDFPageDocumentHelper::get(inpdf).getAllPages()) { |
| 40 | // Find a unique resource name for the new form XObject | 40 | // Find a unique resource name for the new form XObject |
| 41 | QPDFObjectHandle resources = ph.getAttribute("/Resources", true); | 41 | QPDFObjectHandle resources = ph.getAttribute("/Resources", true); |
| 42 | int min_suffix = 1; | 42 | int min_suffix = 1; |
fuzz/qpdf_outlines_fuzzer.cc
| @@ -49,7 +49,7 @@ FuzzHelper::testOutlines() | @@ -49,7 +49,7 @@ FuzzHelper::testOutlines() | ||
| 49 | { | 49 | { |
| 50 | std::shared_ptr<QPDF> q = getQpdf(); | 50 | std::shared_ptr<QPDF> q = getQpdf(); |
| 51 | std::list<std::vector<QPDFOutlineObjectHelper>> queue; | 51 | std::list<std::vector<QPDFOutlineObjectHelper>> queue; |
| 52 | - QPDFOutlineDocumentHelper odh(*q); | 52 | + auto& odh = QPDFOutlineDocumentHelper::get(*q); |
| 53 | queue.push_back(odh.getTopLevelOutlines()); | 53 | queue.push_back(odh.getTopLevelOutlines()); |
| 54 | while (!queue.empty()) { | 54 | while (!queue.empty()) { |
| 55 | for (auto& ol: *(queue.begin())) { | 55 | for (auto& ol: *(queue.begin())) { |
include/qpdf/QPDF.hh
| @@ -63,6 +63,10 @@ class BufferInputSource; | @@ -63,6 +63,10 @@ class BufferInputSource; | ||
| 63 | class QPDFLogger; | 63 | class QPDFLogger; |
| 64 | class QPDFParser; | 64 | class QPDFParser; |
| 65 | class QPDFAcroFormDocumentHelper; | 65 | class QPDFAcroFormDocumentHelper; |
| 66 | +class QPDFEmbeddedFileDocumentHelper; | ||
| 67 | +class QPDFOutlineDocumentHelper; | ||
| 68 | +class QPDFPageDocumentHelper; | ||
| 69 | +class QPDFPageLabelDocumentHelper; | ||
| 66 | 70 | ||
| 67 | class QPDF | 71 | class QPDF |
| 68 | { | 72 | { |
| @@ -799,6 +803,10 @@ class QPDF | @@ -799,6 +803,10 @@ class QPDF | ||
| 799 | 803 | ||
| 800 | inline bool reconstructed_xref() const; | 804 | inline bool reconstructed_xref() const; |
| 801 | inline QPDFAcroFormDocumentHelper& acroform(); | 805 | inline QPDFAcroFormDocumentHelper& acroform(); |
| 806 | + inline QPDFEmbeddedFileDocumentHelper& embedded_files(); | ||
| 807 | + inline QPDFOutlineDocumentHelper& outlines(); | ||
| 808 | + inline QPDFPageDocumentHelper& pages(); | ||
| 809 | + inline QPDFPageLabelDocumentHelper& page_labels(); | ||
| 802 | 810 | ||
| 803 | // For testing only -- do not add to DLL | 811 | // For testing only -- do not add to DLL |
| 804 | static bool test_json_validators(); | 812 | static bool test_json_validators(); |
include/qpdf/QPDFEmbeddedFileDocumentHelper.hh
| @@ -36,6 +36,21 @@ | @@ -36,6 +36,21 @@ | ||
| 36 | class QPDFEmbeddedFileDocumentHelper: public QPDFDocumentHelper | 36 | class QPDFEmbeddedFileDocumentHelper: public QPDFDocumentHelper |
| 37 | { | 37 | { |
| 38 | public: | 38 | public: |
| 39 | + // Get a shared document helper for a given QPDF object. | ||
| 40 | + // | ||
| 41 | + // Retrieving a document helper for a QPDF object rather than creating a new one avoids repeated | ||
| 42 | + // validation of the EmbeddedFiles structure, which can be expensive. | ||
| 43 | + QPDF_DLL | ||
| 44 | + static QPDFEmbeddedFileDocumentHelper& get(QPDF& qpdf); | ||
| 45 | + | ||
| 46 | + // Re-validate the EmbeddedFiles structure. This is useful if you have modified the structure of | ||
| 47 | + // the EmbeddedFiles dictionary in a way that would invalidate the cache. | ||
| 48 | + // | ||
| 49 | + // If repair is true, the document will be repaired if possible if the validation encounters | ||
| 50 | + // errors. | ||
| 51 | + QPDF_DLL | ||
| 52 | + void validate(bool repair = true); | ||
| 53 | + | ||
| 39 | QPDF_DLL | 54 | QPDF_DLL |
| 40 | QPDFEmbeddedFileDocumentHelper(QPDF&); | 55 | QPDFEmbeddedFileDocumentHelper(QPDF&); |
| 41 | 56 |
include/qpdf/QPDFOutlineDocumentHelper.hh
| @@ -38,6 +38,21 @@ | @@ -38,6 +38,21 @@ | ||
| 38 | class QPDFOutlineDocumentHelper: public QPDFDocumentHelper | 38 | class QPDFOutlineDocumentHelper: public QPDFDocumentHelper |
| 39 | { | 39 | { |
| 40 | public: | 40 | public: |
| 41 | + // Get a shared document helper for a given QPDF object. | ||
| 42 | + // | ||
| 43 | + // Retrieving a document helper for a QPDF object rather than creating a new one avoids repeated | ||
| 44 | + // validation of the Acroform structure, which can be expensive. | ||
| 45 | + QPDF_DLL | ||
| 46 | + static QPDFOutlineDocumentHelper& get(QPDF& qpdf); | ||
| 47 | + | ||
| 48 | + // Re-validate the Outlines structure. This is useful if you have modified the structure of the | ||
| 49 | + // Outlines dictionary in a way that would invalidate the cache. | ||
| 50 | + // | ||
| 51 | + // If repair is true, the document will be repaired if possible if the validation encounters | ||
| 52 | + // errors. | ||
| 53 | + QPDF_DLL | ||
| 54 | + void validate(bool repair = true); | ||
| 55 | + | ||
| 41 | QPDF_DLL | 56 | QPDF_DLL |
| 42 | QPDFOutlineDocumentHelper(QPDF&); | 57 | QPDFOutlineDocumentHelper(QPDF&); |
| 43 | 58 |
include/qpdf/QPDFPageDocumentHelper.hh
| @@ -35,6 +35,21 @@ class QPDFAcroFormDocumentHelper; | @@ -35,6 +35,21 @@ class QPDFAcroFormDocumentHelper; | ||
| 35 | class QPDFPageDocumentHelper: public QPDFDocumentHelper | 35 | class QPDFPageDocumentHelper: public QPDFDocumentHelper |
| 36 | { | 36 | { |
| 37 | public: | 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 | QPDF_DLL | 53 | QPDF_DLL |
| 39 | QPDFPageDocumentHelper(QPDF&); | 54 | QPDFPageDocumentHelper(QPDF&); |
| 40 | 55 | ||
| @@ -112,17 +127,7 @@ class QPDFPageDocumentHelper: public QPDFDocumentHelper | @@ -112,17 +127,7 @@ class QPDFPageDocumentHelper: public QPDFDocumentHelper | ||
| 112 | int required_flags, | 127 | int required_flags, |
| 113 | int forbidden_flags); | 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 | std::shared_ptr<Members> m; | 132 | std::shared_ptr<Members> m; |
| 128 | }; | 133 | }; |
include/qpdf/QPDFPageLabelDocumentHelper.hh
| @@ -22,11 +22,10 @@ | @@ -22,11 +22,10 @@ | ||
| 22 | 22 | ||
| 23 | #include <qpdf/QPDFDocumentHelper.hh> | 23 | #include <qpdf/QPDFDocumentHelper.hh> |
| 24 | 24 | ||
| 25 | +#include <qpdf/DLL.h> | ||
| 25 | #include <qpdf/QPDF.hh> | 26 | #include <qpdf/QPDF.hh> |
| 26 | -#include <qpdf/QPDFNumberTreeObjectHelper.hh> | ||
| 27 | -#include <vector> | ||
| 28 | 27 | ||
| 29 | -#include <qpdf/DLL.h> | 28 | +#include <vector> |
| 30 | 29 | ||
| 31 | // Page labels are discussed in the PDF spec (ISO-32000) in section 12.4.2. | 30 | // Page labels are discussed in the PDF spec (ISO-32000) in section 12.4.2. |
| 32 | // | 31 | // |
| @@ -42,6 +41,21 @@ | @@ -42,6 +41,21 @@ | ||
| 42 | class QPDFPageLabelDocumentHelper: public QPDFDocumentHelper | 41 | class QPDFPageLabelDocumentHelper: public QPDFDocumentHelper |
| 43 | { | 42 | { |
| 44 | public: | 43 | public: |
| 44 | + // Get a shared document helper for a given QPDF object. | ||
| 45 | + // | ||
| 46 | + // Retrieving a document helper for a QPDF object rather than creating a new one avoids repeated | ||
| 47 | + // validation of the PageLabels structure, which can be expensive. | ||
| 48 | + QPDF_DLL | ||
| 49 | + static QPDFPageLabelDocumentHelper& get(QPDF& qpdf); | ||
| 50 | + | ||
| 51 | + // Re-validate the PageLabels structure. This is useful if you have modified the structure of | ||
| 52 | + // the PageLabels dictionary in a way that could have invalidated the structure. | ||
| 53 | + // | ||
| 54 | + // If repair is true, the document will be repaired if possible if the validation encounters | ||
| 55 | + // errors. | ||
| 56 | + QPDF_DLL | ||
| 57 | + void validate(bool repair = true); | ||
| 58 | + | ||
| 45 | QPDF_DLL | 59 | QPDF_DLL |
| 46 | QPDFPageLabelDocumentHelper(QPDF&); | 60 | QPDFPageLabelDocumentHelper(QPDF&); |
| 47 | 61 |
libqpdf/QPDFEmbeddedFileDocumentHelper.cc
| 1 | #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> | 1 | #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> |
| 2 | 2 | ||
| 3 | +#include <qpdf/QPDFNameTreeObjectHelper.hh> | ||
| 4 | +#include <qpdf/QPDF_private.hh> | ||
| 5 | + | ||
| 3 | // File attachments are stored in the /EmbeddedFiles (name tree) key of the /Names dictionary from | 6 | // File attachments are stored in the /EmbeddedFiles (name tree) key of the /Names dictionary from |
| 4 | // the document catalog. Each entry points to a /FileSpec, which in turn points to one more Embedded | 7 | // the document catalog. Each entry points to a /FileSpec, which in turn points to one more Embedded |
| 5 | // File Streams. Note that file specs can appear in other places as well, such as file attachment | 8 | // File Streams. Note that file specs can appear in other places as well, such as file attachment |
| @@ -44,6 +47,19 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) : | @@ -44,6 +47,19 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) : | ||
| 44 | QPDFDocumentHelper(qpdf), | 47 | QPDFDocumentHelper(qpdf), |
| 45 | m(std::make_shared<Members>()) | 48 | m(std::make_shared<Members>()) |
| 46 | { | 49 | { |
| 50 | + validate(); | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +QPDFEmbeddedFileDocumentHelper& | ||
| 54 | +QPDFEmbeddedFileDocumentHelper::get(QPDF& qpdf) | ||
| 55 | +{ | ||
| 56 | + return qpdf.embedded_files(); | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +void | ||
| 60 | +QPDFEmbeddedFileDocumentHelper::validate(bool repair) | ||
| 61 | +{ | ||
| 62 | + m->embedded_files.reset(); | ||
| 47 | auto names = qpdf.getRoot().getKey("/Names"); | 63 | auto names = qpdf.getRoot().getKey("/Names"); |
| 48 | if (names.isDictionary()) { | 64 | if (names.isDictionary()) { |
| 49 | auto embedded_files = names.getKey("/EmbeddedFiles"); | 65 | auto embedded_files = names.getKey("/EmbeddedFiles"); |
| @@ -53,7 +69,7 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) : | @@ -53,7 +69,7 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) : | ||
| 53 | qpdf, | 69 | qpdf, |
| 54 | [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, | 70 | [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, |
| 55 | true); | 71 | true); |
| 56 | - m->embedded_files->validate(); | 72 | + m->embedded_files->validate(repair); |
| 57 | } | 73 | } |
| 58 | } | 74 | } |
| 59 | } | 75 | } |
libqpdf/QPDFJob.cc
| @@ -13,15 +13,10 @@ | @@ -13,15 +13,10 @@ | ||
| 13 | #include <qpdf/Pl_StdioFile.hh> | 13 | #include <qpdf/Pl_StdioFile.hh> |
| 14 | #include <qpdf/Pl_String.hh> | 14 | #include <qpdf/Pl_String.hh> |
| 15 | #include <qpdf/QIntC.hh> | 15 | #include <qpdf/QIntC.hh> |
| 16 | -#include <qpdf/QPDFAcroFormDocumentHelper.hh> | ||
| 17 | #include <qpdf/QPDFCryptoProvider.hh> | 16 | #include <qpdf/QPDFCryptoProvider.hh> |
| 18 | -#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> | ||
| 19 | #include <qpdf/QPDFExc.hh> | 17 | #include <qpdf/QPDFExc.hh> |
| 20 | #include <qpdf/QPDFLogger.hh> | 18 | #include <qpdf/QPDFLogger.hh> |
| 21 | #include <qpdf/QPDFObjectHandle_private.hh> | 19 | #include <qpdf/QPDFObjectHandle_private.hh> |
| 22 | -#include <qpdf/QPDFOutlineDocumentHelper.hh> | ||
| 23 | -#include <qpdf/QPDFPageDocumentHelper.hh> | ||
| 24 | -#include <qpdf/QPDFPageLabelDocumentHelper.hh> | ||
| 25 | #include <qpdf/QPDFPageObjectHelper.hh> | 20 | #include <qpdf/QPDFPageObjectHelper.hh> |
| 26 | #include <qpdf/QPDFSystemError.hh> | 21 | #include <qpdf/QPDFSystemError.hh> |
| 27 | #include <qpdf/QPDFUsage.hh> | 22 | #include <qpdf/QPDFUsage.hh> |
| @@ -784,6 +779,14 @@ QPDFJob::doCheck(QPDF& pdf) | @@ -784,6 +779,14 @@ QPDFJob::doCheck(QPDF& pdf) | ||
| 784 | cout << "File is not linearized\n"; | 779 | cout << "File is not linearized\n"; |
| 785 | } | 780 | } |
| 786 | 781 | ||
| 782 | + // Create all document helper to trigger any validations they carry out. | ||
| 783 | + auto& pages = pdf.pages(); | ||
| 784 | + (void)pdf.acroform(); | ||
| 785 | + (void)pdf.embedded_files(); | ||
| 786 | + (void)pdf.page_labels(); | ||
| 787 | + (void)pdf.outlines().resolveNamedDest(QPDFObjectHandle::newString("dummy")); | ||
| 788 | + (void)pdf.outlines().getOutlinesForPage(pages.getAllPages().at(0)); | ||
| 789 | + | ||
| 787 | // Write the file to nowhere, uncompressing streams. This causes full file traversal and | 790 | // Write the file to nowhere, uncompressing streams. This causes full file traversal and |
| 788 | // decoding of all streams we can decode. | 791 | // decoding of all streams we can decode. |
| 789 | QPDFWriter w(pdf); | 792 | QPDFWriter w(pdf); |
| @@ -794,7 +797,7 @@ QPDFJob::doCheck(QPDF& pdf) | @@ -794,7 +797,7 @@ QPDFJob::doCheck(QPDF& pdf) | ||
| 794 | 797 | ||
| 795 | // Parse all content streams | 798 | // Parse all content streams |
| 796 | int pageno = 0; | 799 | int pageno = 0; |
| 797 | - for (auto& page: QPDFPageDocumentHelper(pdf).getAllPages()) { | 800 | + for (auto& page: pages.getAllPages()) { |
| 798 | ++pageno; | 801 | ++pageno; |
| 799 | try { | 802 | try { |
| 800 | page.parseContents(nullptr); | 803 | page.parseContents(nullptr); |
| @@ -862,7 +865,7 @@ QPDFJob::doShowPages(QPDF& pdf) | @@ -862,7 +865,7 @@ QPDFJob::doShowPages(QPDF& pdf) | ||
| 862 | { | 865 | { |
| 863 | int pageno = 0; | 866 | int pageno = 0; |
| 864 | auto& cout = *m->log->getInfo(); | 867 | auto& cout = *m->log->getInfo(); |
| 865 | - for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { | 868 | + for (auto& ph: pdf.pages().getAllPages()) { |
| 866 | QPDFObjectHandle page = ph.getObjectHandle(); | 869 | QPDFObjectHandle page = ph.getObjectHandle(); |
| 867 | ++pageno; | 870 | ++pageno; |
| 868 | 871 | ||
| @@ -894,7 +897,7 @@ QPDFJob::doShowPages(QPDF& pdf) | @@ -894,7 +897,7 @@ QPDFJob::doShowPages(QPDF& pdf) | ||
| 894 | void | 897 | void |
| 895 | QPDFJob::doListAttachments(QPDF& pdf) | 898 | QPDFJob::doListAttachments(QPDF& pdf) |
| 896 | { | 899 | { |
| 897 | - QPDFEmbeddedFileDocumentHelper efdh(pdf); | 900 | + auto& efdh = pdf.embedded_files(); |
| 898 | if (efdh.hasEmbeddedFiles()) { | 901 | if (efdh.hasEmbeddedFiles()) { |
| 899 | for (auto const& i: efdh.getEmbeddedFiles()) { | 902 | for (auto const& i: efdh.getEmbeddedFiles()) { |
| 900 | std::string const& key = i.first; | 903 | std::string const& key = i.first; |
| @@ -934,7 +937,7 @@ QPDFJob::doListAttachments(QPDF& pdf) | @@ -934,7 +937,7 @@ QPDFJob::doListAttachments(QPDF& pdf) | ||
| 934 | void | 937 | void |
| 935 | QPDFJob::doShowAttachment(QPDF& pdf) | 938 | QPDFJob::doShowAttachment(QPDF& pdf) |
| 936 | { | 939 | { |
| 937 | - QPDFEmbeddedFileDocumentHelper efdh(pdf); | 940 | + auto& efdh = pdf.embedded_files(); |
| 938 | auto fs = efdh.getEmbeddedFile(m->attachment_to_show); | 941 | auto fs = efdh.getEmbeddedFile(m->attachment_to_show); |
| 939 | if (!fs) { | 942 | if (!fs) { |
| 940 | throw std::runtime_error("attachment " + m->attachment_to_show + " not found"); | 943 | throw std::runtime_error("attachment " + m->attachment_to_show + " not found"); |
| @@ -1053,10 +1056,10 @@ QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf) | @@ -1053,10 +1056,10 @@ QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf) | ||
| 1053 | JSON::writeDictionaryKey(p, first, "pages", 1); | 1056 | JSON::writeDictionaryKey(p, first, "pages", 1); |
| 1054 | bool first_page = true; | 1057 | bool first_page = true; |
| 1055 | JSON::writeArrayOpen(p, first_page, 2); | 1058 | JSON::writeArrayOpen(p, first_page, 2); |
| 1056 | - QPDFPageLabelDocumentHelper pldh(pdf); | ||
| 1057 | - QPDFOutlineDocumentHelper odh(pdf); | 1059 | + auto& pldh = pdf.page_labels(); |
| 1060 | + auto& odh = pdf.outlines(); | ||
| 1058 | int pageno = -1; | 1061 | int pageno = -1; |
| 1059 | - for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { | 1062 | + for (auto& ph: pdf.pages().getAllPages()) { |
| 1060 | ++pageno; | 1063 | ++pageno; |
| 1061 | JSON j_page = JSON::makeDictionary(); | 1064 | JSON j_page = JSON::makeDictionary(); |
| 1062 | QPDFObjectHandle page = ph.getObjectHandle(); | 1065 | QPDFObjectHandle page = ph.getObjectHandle(); |
| @@ -1116,8 +1119,8 @@ void | @@ -1116,8 +1119,8 @@ void | ||
| 1116 | QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf) | 1119 | QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf) |
| 1117 | { | 1120 | { |
| 1118 | JSON j_labels = JSON::makeArray(); | 1121 | JSON j_labels = JSON::makeArray(); |
| 1119 | - QPDFPageLabelDocumentHelper pldh(pdf); | ||
| 1120 | - long long npages = QIntC::to_longlong(QPDFPageDocumentHelper(pdf).getAllPages().size()); | 1122 | + auto& pldh = pdf.page_labels(); |
| 1123 | + long long npages = QIntC::to_longlong(pdf.pages().getAllPages().size()); | ||
| 1121 | if (pldh.hasPageLabels()) { | 1124 | if (pldh.hasPageLabels()) { |
| 1122 | std::vector<QPDFObjectHandle> labels; | 1125 | std::vector<QPDFObjectHandle> labels; |
| 1123 | pldh.getLabelsForPageRange(0, npages - 1, 0, labels); | 1126 | pldh.getLabelsForPageRange(0, npages - 1, 0, labels); |
| @@ -1165,14 +1168,13 @@ QPDFJob::doJSONOutlines(Pipeline* p, bool& first, QPDF& pdf) | @@ -1165,14 +1168,13 @@ QPDFJob::doJSONOutlines(Pipeline* p, bool& first, QPDF& pdf) | ||
| 1165 | { | 1168 | { |
| 1166 | std::map<QPDFObjGen, int> page_numbers; | 1169 | std::map<QPDFObjGen, int> page_numbers; |
| 1167 | int n = 0; | 1170 | int n = 0; |
| 1168 | - for (auto const& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { | 1171 | + for (auto const& ph: pdf.pages().getAllPages()) { |
| 1169 | QPDFObjectHandle oh = ph.getObjectHandle(); | 1172 | QPDFObjectHandle oh = ph.getObjectHandle(); |
| 1170 | page_numbers[oh.getObjGen()] = ++n; | 1173 | page_numbers[oh.getObjGen()] = ++n; |
| 1171 | } | 1174 | } |
| 1172 | 1175 | ||
| 1173 | JSON j_outlines = JSON::makeArray(); | 1176 | JSON j_outlines = JSON::makeArray(); |
| 1174 | - QPDFOutlineDocumentHelper odh(pdf); | ||
| 1175 | - addOutlinesToJson(odh.getTopLevelOutlines(), j_outlines, page_numbers); | 1177 | + addOutlinesToJson(pdf.outlines().getTopLevelOutlines(), j_outlines, page_numbers); |
| 1176 | JSON::writeDictionaryItem(p, first, "outlines", j_outlines, 1); | 1178 | JSON::writeDictionaryItem(p, first, "outlines", j_outlines, 1); |
| 1177 | } | 1179 | } |
| 1178 | 1180 | ||
| @@ -1185,7 +1187,7 @@ QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf) | @@ -1185,7 +1187,7 @@ QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf) | ||
| 1185 | j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances())); | 1187 | j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances())); |
| 1186 | JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray()); | 1188 | JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray()); |
| 1187 | int pagepos1 = 0; | 1189 | int pagepos1 = 0; |
| 1188 | - for (auto const& page: QPDFPageDocumentHelper(pdf).getAllPages()) { | 1190 | + for (auto const& page: pdf.pages().getAllPages()) { |
| 1189 | ++pagepos1; | 1191 | ++pagepos1; |
| 1190 | for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) { | 1192 | for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) { |
| 1191 | QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh); | 1193 | QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh); |
| @@ -1321,7 +1323,7 @@ QPDFJob::doJSONAttachments(Pipeline* p, bool& first, QPDF& pdf) | @@ -1321,7 +1323,7 @@ QPDFJob::doJSONAttachments(Pipeline* p, bool& first, QPDF& pdf) | ||
| 1321 | }; | 1323 | }; |
| 1322 | 1324 | ||
| 1323 | JSON j_attachments = JSON::makeDictionary(); | 1325 | JSON j_attachments = JSON::makeDictionary(); |
| 1324 | - QPDFEmbeddedFileDocumentHelper efdh(pdf); | 1326 | + auto& efdh = pdf.embedded_files(); |
| 1325 | for (auto const& iter: efdh.getEmbeddedFiles()) { | 1327 | for (auto const& iter: efdh.getEmbeddedFiles()) { |
| 1326 | std::string const& key = iter.first; | 1328 | std::string const& key = iter.first; |
| 1327 | auto fsoh = iter.second; | 1329 | auto fsoh = iter.second; |
| @@ -1862,7 +1864,7 @@ QPDFJob::processInputSource( | @@ -1862,7 +1864,7 @@ QPDFJob::processInputSource( | ||
| 1862 | void | 1864 | void |
| 1863 | QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) | 1865 | QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) |
| 1864 | { | 1866 | { |
| 1865 | - QPDFPageDocumentHelper main_pdh(pdf); | 1867 | + auto& main_pdh = pdf.pages(); |
| 1866 | int main_npages = QIntC::to_int(main_pdh.getAllPages().size()); | 1868 | int main_npages = QIntC::to_int(main_pdh.getAllPages().size()); |
| 1867 | processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false); | 1869 | processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false); |
| 1868 | QPDFPageDocumentHelper uo_pdh(*(uo->pdf)); | 1870 | QPDFPageDocumentHelper uo_pdh(*(uo->pdf)); |
| @@ -2073,7 +2075,7 @@ void | @@ -2073,7 +2075,7 @@ void | ||
| 2073 | QPDFJob::addAttachments(QPDF& pdf) | 2075 | QPDFJob::addAttachments(QPDF& pdf) |
| 2074 | { | 2076 | { |
| 2075 | maybe_set_pagemode(pdf, "/UseAttachments"); | 2077 | maybe_set_pagemode(pdf, "/UseAttachments"); |
| 2076 | - QPDFEmbeddedFileDocumentHelper efdh(pdf); | 2078 | + auto& efdh = pdf.embedded_files(); |
| 2077 | std::vector<std::string> duplicated_keys; | 2079 | std::vector<std::string> duplicated_keys; |
| 2078 | for (auto const& to_add: m->attachments_to_add) { | 2080 | for (auto const& to_add: m->attachments_to_add) { |
| 2079 | if ((!to_add.replace) && efdh.getEmbeddedFile(to_add.key)) { | 2081 | if ((!to_add.replace) && efdh.getEmbeddedFile(to_add.key)) { |
| @@ -2117,7 +2119,7 @@ void | @@ -2117,7 +2119,7 @@ void | ||
| 2117 | QPDFJob::copyAttachments(QPDF& pdf) | 2119 | QPDFJob::copyAttachments(QPDF& pdf) |
| 2118 | { | 2120 | { |
| 2119 | maybe_set_pagemode(pdf, "/UseAttachments"); | 2121 | maybe_set_pagemode(pdf, "/UseAttachments"); |
| 2120 | - QPDFEmbeddedFileDocumentHelper efdh(pdf); | 2122 | + auto& efdh = pdf.embedded_files(); |
| 2121 | std::vector<std::string> duplicates; | 2123 | std::vector<std::string> duplicates; |
| 2122 | for (auto const& to_copy: m->attachments_to_copy) { | 2124 | for (auto const& to_copy: m->attachments_to_copy) { |
| 2123 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { | 2125 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { |
| @@ -2125,7 +2127,7 @@ QPDFJob::copyAttachments(QPDF& pdf) | @@ -2125,7 +2127,7 @@ QPDFJob::copyAttachments(QPDF& pdf) | ||
| 2125 | }); | 2127 | }); |
| 2126 | std::unique_ptr<QPDF> other; | 2128 | std::unique_ptr<QPDF> other; |
| 2127 | processFile(other, to_copy.path.c_str(), to_copy.password.c_str(), false, false); | 2129 | processFile(other, to_copy.path.c_str(), to_copy.password.c_str(), false, false); |
| 2128 | - QPDFEmbeddedFileDocumentHelper other_efdh(*other); | 2130 | + auto& other_efdh = other->embedded_files(); |
| 2129 | auto other_attachments = other_efdh.getEmbeddedFiles(); | 2131 | auto other_attachments = other_efdh.getEmbeddedFiles(); |
| 2130 | for (auto const& iter: other_attachments) { | 2132 | for (auto const& iter: other_attachments) { |
| 2131 | std::string new_key = to_copy.prefix + iter.first; | 2133 | std::string new_key = to_copy.prefix + iter.first; |
| @@ -2259,7 +2261,7 @@ QPDFJob::handleTransformations(QPDF& pdf) | @@ -2259,7 +2261,7 @@ QPDFJob::handleTransformations(QPDF& pdf) | ||
| 2259 | pdf.getRoot().replaceKey("/PageLabels", page_labels); | 2261 | pdf.getRoot().replaceKey("/PageLabels", page_labels); |
| 2260 | } | 2262 | } |
| 2261 | if (!m->attachments_to_remove.empty()) { | 2263 | if (!m->attachments_to_remove.empty()) { |
| 2262 | - QPDFEmbeddedFileDocumentHelper efdh(pdf); | 2264 | + auto& efdh = pdf.embedded_files(); |
| 2263 | for (auto const& key: m->attachments_to_remove) { | 2265 | for (auto const& key: m->attachments_to_remove) { |
| 2264 | if (efdh.removeEmbeddedFile(key)) { | 2266 | if (efdh.removeEmbeddedFile(key)) { |
| 2265 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { | 2267 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { |
| @@ -2548,7 +2550,7 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea | @@ -2548,7 +2550,7 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea | ||
| 2548 | cis = page_spec_cfis[page_data.filename]; | 2550 | cis = page_spec_cfis[page_data.filename]; |
| 2549 | cis->stayOpen(true); | 2551 | cis->stayOpen(true); |
| 2550 | } | 2552 | } |
| 2551 | - QPDFPageLabelDocumentHelper pldh(*page_data.qpdf); | 2553 | + auto& pldh = page_data.qpdf->page_labels(); |
| 2552 | auto& other_afdh = page_data.qpdf->acroform(); | 2554 | auto& other_afdh = page_data.qpdf->acroform(); |
| 2553 | if (pldh.hasPageLabels()) { | 2555 | if (pldh.hasPageLabels()) { |
| 2554 | any_page_labels = true; | 2556 | any_page_labels = true; |
| @@ -2992,7 +2994,7 @@ QPDFJob::doSplitPages(QPDF& pdf) | @@ -2992,7 +2994,7 @@ QPDFJob::doSplitPages(QPDF& pdf) | ||
| 2992 | QPDFPageDocumentHelper dh(pdf); | 2994 | QPDFPageDocumentHelper dh(pdf); |
| 2993 | dh.removeUnreferencedResources(); | 2995 | dh.removeUnreferencedResources(); |
| 2994 | } | 2996 | } |
| 2995 | - QPDFPageLabelDocumentHelper pldh(pdf); | 2997 | + auto& pldh = pdf.page_labels(); |
| 2996 | auto& afdh = pdf.acroform(); | 2998 | auto& afdh = pdf.acroform(); |
| 2997 | std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages(); | 2999 | std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages(); |
| 2998 | size_t pageno_len = std::to_string(pages.size()).length(); | 3000 | size_t pageno_len = std::to_string(pages.size()).length(); |
libqpdf/QPDFOutlineDocumentHelper.cc
| 1 | #include <qpdf/QPDFOutlineDocumentHelper.hh> | 1 | #include <qpdf/QPDFOutlineDocumentHelper.hh> |
| 2 | 2 | ||
| 3 | #include <qpdf/QPDFObjectHandle_private.hh> | 3 | #include <qpdf/QPDFObjectHandle_private.hh> |
| 4 | +#include <qpdf/QPDF_private.hh> | ||
| 4 | #include <qpdf/QTC.hh> | 5 | #include <qpdf/QTC.hh> |
| 5 | 6 | ||
| 6 | class QPDFOutlineDocumentHelper::Members | 7 | class QPDFOutlineDocumentHelper::Members |
| @@ -27,6 +28,21 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) : | @@ -27,6 +28,21 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) : | ||
| 27 | QPDFDocumentHelper(qpdf), | 28 | QPDFDocumentHelper(qpdf), |
| 28 | m(std::make_shared<Members>()) | 29 | m(std::make_shared<Members>()) |
| 29 | { | 30 | { |
| 31 | + validate(); | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +QPDFOutlineDocumentHelper& | ||
| 35 | +QPDFOutlineDocumentHelper::get(QPDF& qpdf) | ||
| 36 | +{ | ||
| 37 | + return qpdf.outlines(); | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +void | ||
| 41 | +QPDFOutlineDocumentHelper::validate(bool repair) | ||
| 42 | +{ | ||
| 43 | + m->outlines.clear(); | ||
| 44 | + m->names_dest = nullptr; | ||
| 45 | + | ||
| 30 | QPDFObjectHandle root = qpdf.getRoot(); | 46 | QPDFObjectHandle root = qpdf.getRoot(); |
| 31 | if (!root.hasKey("/Outlines")) { | 47 | if (!root.hasKey("/Outlines")) { |
| 32 | return; | 48 | return; |
| @@ -37,7 +53,11 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) : | @@ -37,7 +53,11 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) : | ||
| 37 | } | 53 | } |
| 38 | QPDFObjectHandle cur = outlines.getKey("/First"); | 54 | QPDFObjectHandle cur = outlines.getKey("/First"); |
| 39 | QPDFObjGen::set seen; | 55 | QPDFObjGen::set seen; |
| 40 | - while (!cur.null() && seen.add(cur)) { | 56 | + while (!cur.null()) { |
| 57 | + if (!seen.add(cur)) { | ||
| 58 | + cur.warn("Loop detected loop in /Outlines tree"); | ||
| 59 | + return; | ||
| 60 | + } | ||
| 41 | m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1)); | 61 | m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1)); |
| 42 | cur = cur.getKey("/Next"); | 62 | cur = cur.getKey("/Next"); |
| 43 | } | 63 | } |
libqpdf/QPDFOutlineObjectHelper.cc
| @@ -20,15 +20,20 @@ QPDFOutlineObjectHelper::QPDFOutlineObjectHelper( | @@ -20,15 +20,20 @@ QPDFOutlineObjectHelper::QPDFOutlineObjectHelper( | ||
| 20 | return; | 20 | return; |
| 21 | } | 21 | } |
| 22 | if (QPDFOutlineDocumentHelper::Accessor::checkSeen(m->dh, a_oh.getObjGen())) { | 22 | if (QPDFOutlineDocumentHelper::Accessor::checkSeen(m->dh, a_oh.getObjGen())) { |
| 23 | + a_oh.warn("Loop detected loop in /Outlines tree"); | ||
| 23 | return; | 24 | return; |
| 24 | } | 25 | } |
| 25 | 26 | ||
| 26 | QPDFObjGen::set children; | 27 | QPDFObjGen::set children; |
| 27 | QPDFObjectHandle cur = a_oh.getKey("/First"); | 28 | QPDFObjectHandle cur = a_oh.getKey("/First"); |
| 28 | - while (!cur.null() && cur.isIndirect() && children.add(cur)) { | 29 | + while (!cur.null() && cur.isIndirect()) { |
| 30 | + if (!children.add(cur)) { | ||
| 31 | + cur.warn("Loop detected loop in /Outlines tree"); | ||
| 32 | + break; | ||
| 33 | + } | ||
| 29 | QPDFOutlineObjectHelper new_ooh(cur, dh, 1 + depth); | 34 | QPDFOutlineObjectHelper new_ooh(cur, dh, 1 + depth); |
| 30 | new_ooh.m->parent = std::make_shared<QPDFOutlineObjectHelper>(*this); | 35 | new_ooh.m->parent = std::make_shared<QPDFOutlineObjectHelper>(*this); |
| 31 | - m->kids.push_back(new_ooh); | 36 | + m->kids.emplace_back(new_ooh); |
| 32 | cur = cur.getKey("/Next"); | 37 | cur = cur.getKey("/Next"); |
| 33 | } | 38 | } |
| 34 | } | 39 | } |
libqpdf/QPDFPageDocumentHelper.cc
| @@ -6,11 +6,26 @@ | @@ -6,11 +6,26 @@ | ||
| 6 | #include <qpdf/QTC.hh> | 6 | #include <qpdf/QTC.hh> |
| 7 | #include <qpdf/QUtil.hh> | 7 | #include <qpdf/QUtil.hh> |
| 8 | 8 | ||
| 9 | +class QPDFPageDocumentHelper::Members | ||
| 10 | +{ | ||
| 11 | +}; | ||
| 12 | + | ||
| 9 | QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) : | 13 | QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) : |
| 10 | QPDFDocumentHelper(qpdf) | 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 | std::vector<QPDFPageObjectHelper> | 29 | std::vector<QPDFPageObjectHelper> |
| 15 | QPDFPageDocumentHelper::getAllPages() | 30 | QPDFPageDocumentHelper::getAllPages() |
| 16 | { | 31 | { |
libqpdf/QPDFPageLabelDocumentHelper.cc
| 1 | #include <qpdf/QPDFPageLabelDocumentHelper.hh> | 1 | #include <qpdf/QPDFPageLabelDocumentHelper.hh> |
| 2 | 2 | ||
| 3 | +#include <qpdf/QPDFNumberTreeObjectHelper.hh> | ||
| 3 | #include <qpdf/QPDFObjectHandle_private.hh> | 4 | #include <qpdf/QPDFObjectHandle_private.hh> |
| 5 | +#include <qpdf/QPDF_private.hh> | ||
| 6 | + | ||
| 7 | +using namespace qpdf; | ||
| 4 | 8 | ||
| 5 | class QPDFPageLabelDocumentHelper::Members | 9 | class QPDFPageLabelDocumentHelper::Members |
| 6 | { | 10 | { |
| @@ -16,14 +20,23 @@ QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF& qpdf) : | @@ -16,14 +20,23 @@ QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF& qpdf) : | ||
| 16 | QPDFDocumentHelper(qpdf), | 20 | QPDFDocumentHelper(qpdf), |
| 17 | m(std::make_shared<Members>()) | 21 | m(std::make_shared<Members>()) |
| 18 | { | 22 | { |
| 19 | - QPDFObjectHandle root = qpdf.getRoot(); | ||
| 20 | - if (root.hasKey("/PageLabels")) { | 23 | + validate(); |
| 24 | +} | ||
| 25 | + | ||
| 26 | +QPDFPageLabelDocumentHelper& | ||
| 27 | +QPDFPageLabelDocumentHelper::get(QPDF& qpdf) | ||
| 28 | +{ | ||
| 29 | + return qpdf.page_labels(); | ||
| 30 | +} | ||
| 31 | + | ||
| 32 | +void | ||
| 33 | +QPDFPageLabelDocumentHelper::validate(bool repair) | ||
| 34 | +{ | ||
| 35 | + m->labels = nullptr; | ||
| 36 | + if (Dictionary labels = qpdf.getRoot()["/PageLabels"]) { | ||
| 21 | m->labels = std::make_unique<QPDFNumberTreeObjectHelper>( | 37 | m->labels = std::make_unique<QPDFNumberTreeObjectHelper>( |
| 22 | - root.getKey("/PageLabels"), | ||
| 23 | - this->qpdf, | ||
| 24 | - [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, | ||
| 25 | - true); | ||
| 26 | - m->labels->validate(); | 38 | + labels, qpdf, [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, true); |
| 39 | + m->labels->validate(repair); | ||
| 27 | } | 40 | } |
| 28 | } | 41 | } |
| 29 | 42 |
libqpdf/qpdf/QPDF_private.hh
| @@ -4,7 +4,11 @@ | @@ -4,7 +4,11 @@ | ||
| 4 | #include <qpdf/QPDF.hh> | 4 | #include <qpdf/QPDF.hh> |
| 5 | 5 | ||
| 6 | #include <qpdf/QPDFAcroFormDocumentHelper.hh> | 6 | #include <qpdf/QPDFAcroFormDocumentHelper.hh> |
| 7 | +#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> | ||
| 7 | #include <qpdf/QPDFObject_private.hh> | 8 | #include <qpdf/QPDFObject_private.hh> |
| 9 | +#include <qpdf/QPDFOutlineDocumentHelper.hh> | ||
| 10 | +#include <qpdf/QPDFPageDocumentHelper.hh> | ||
| 11 | +#include <qpdf/QPDFPageLabelDocumentHelper.hh> | ||
| 8 | #include <qpdf/QPDFTokenizer_private.hh> | 12 | #include <qpdf/QPDFTokenizer_private.hh> |
| 9 | 13 | ||
| 10 | using namespace qpdf; | 14 | using namespace qpdf; |
| @@ -553,6 +557,10 @@ class QPDF::Members | @@ -553,6 +557,10 @@ class QPDF::Members | ||
| 553 | 557 | ||
| 554 | // Document Helpers; | 558 | // Document Helpers; |
| 555 | std::unique_ptr<QPDFAcroFormDocumentHelper> acroform; | 559 | std::unique_ptr<QPDFAcroFormDocumentHelper> acroform; |
| 560 | + std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files; | ||
| 561 | + std::unique_ptr<QPDFOutlineDocumentHelper> outlines; | ||
| 562 | + std::unique_ptr<QPDFPageDocumentHelper> pages; | ||
| 563 | + std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels; | ||
| 556 | }; | 564 | }; |
| 557 | 565 | ||
| 558 | // JobSetter class is restricted to QPDFJob. | 566 | // JobSetter class is restricted to QPDFJob. |
| @@ -584,4 +592,40 @@ QPDF::acroform() | @@ -584,4 +592,40 @@ QPDF::acroform() | ||
| 584 | return *m->acroform; | 592 | return *m->acroform; |
| 585 | } | 593 | } |
| 586 | 594 | ||
| 595 | +inline QPDFEmbeddedFileDocumentHelper& | ||
| 596 | +QPDF::embedded_files() | ||
| 597 | +{ | ||
| 598 | + if (!m->embedded_files) { | ||
| 599 | + m->embedded_files = std::make_unique<QPDFEmbeddedFileDocumentHelper>(*this); | ||
| 600 | + } | ||
| 601 | + return *m->embedded_files; | ||
| 602 | +} | ||
| 603 | + | ||
| 604 | +inline QPDFOutlineDocumentHelper& | ||
| 605 | +QPDF::outlines() | ||
| 606 | +{ | ||
| 607 | + if (!m->outlines) { | ||
| 608 | + m->outlines = std::make_unique<QPDFOutlineDocumentHelper>(*this); | ||
| 609 | + } | ||
| 610 | + return *m->outlines; | ||
| 611 | +} | ||
| 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 | + | ||
| 622 | +inline QPDFPageLabelDocumentHelper& | ||
| 623 | +QPDF::page_labels() | ||
| 624 | +{ | ||
| 625 | + if (!m->page_labels) { | ||
| 626 | + m->page_labels = std::make_unique<QPDFPageLabelDocumentHelper>(*this); | ||
| 627 | + } | ||
| 628 | + return *m->page_labels; | ||
| 629 | +} | ||
| 630 | + | ||
| 587 | #endif // QPDF_PRIVATE_HH | 631 | #endif // QPDF_PRIVATE_HH |
manual/release-notes.rst
| @@ -26,19 +26,29 @@ more detail. | @@ -26,19 +26,29 @@ more detail. | ||
| 26 | - Library Enhancements | 26 | - Library Enhancements |
| 27 | 27 | ||
| 28 | - Add ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` | 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 | validate the values in the tree. | 30 | validate the values in the tree. |
| 31 | 31 | ||
| 32 | - Add new ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` | 32 | - Add new ``QPDFNameTreeObjectHelper`` and ``QPDFNumberTreeObjectHelper`` |
| 33 | ``validate`` method to validate and optionally repair the name/number | 33 | ``validate`` method to validate and optionally repair the name/number |
| 34 | tree. | 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 | - CLI Enhancements | 44 | - CLI Enhancements |
| 37 | 45 | ||
| 38 | - Disallow option :qpdf:ref:`--deterministic-id` to be used together | 46 | - Disallow option :qpdf:ref:`--deterministic-id` to be used together |
| 39 | with the incompatible options :qpdf:ref:`--encrypt` or | 47 | with the incompatible options :qpdf:ref:`--encrypt` or |
| 40 | :qpdf:ref:`--copy-encryption`. | 48 | :qpdf:ref:`--copy-encryption`. |
| 41 | 49 | ||
| 50 | + - Option :qpdf:ref:`--check` now includes additional basic checks of the | ||
| 51 | + AcroForm, Dests, Outlines, and PageLabels structures. | ||
| 42 | 52 | ||
| 43 | - Other enhancements | 53 | - Other enhancements |
| 44 | 54 |
qpdf/qtest/outlines.test
| @@ -21,7 +21,7 @@ my @outline_files = ( | @@ -21,7 +21,7 @@ my @outline_files = ( | ||
| 21 | 'outlines-with-old-root-dests-dict', | 21 | 'outlines-with-old-root-dests-dict', |
| 22 | 'outlines-with-loop', | 22 | 'outlines-with-loop', |
| 23 | ); | 23 | ); |
| 24 | -my $n_tests = scalar(@outline_files); | 24 | +my $n_tests = scalar(@outline_files) + 1; |
| 25 | foreach my $f (@outline_files) | 25 | foreach my $f (@outline_files) |
| 26 | { | 26 | { |
| 27 | $td->runtest("outlines: $f", | 27 | $td->runtest("outlines: $f", |
| @@ -30,5 +30,10 @@ foreach my $f (@outline_files) | @@ -30,5 +30,10 @@ foreach my $f (@outline_files) | ||
| 30 | $td->NORMALIZE_NEWLINES); | 30 | $td->NORMALIZE_NEWLINES); |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | +$td->runtest("outlines: outlines-with-loop --check", | ||
| 34 | + {$td->COMMAND => "qpdf --check outlines-with-loop.pdf"}, | ||
| 35 | + {$td->FILE => "outlines-with-loop-check.out", $td->EXIT_STATUS => 3}, | ||
| 36 | + $td->NORMALIZE_NEWLINES); | ||
| 37 | + | ||
| 33 | cleanup(); | 38 | cleanup(); |
| 34 | $td->report($n_tests); | 39 | $td->report($n_tests); |
qpdf/qtest/page-labels.test
| @@ -14,7 +14,7 @@ cleanup(); | @@ -14,7 +14,7 @@ cleanup(); | ||
| 14 | 14 | ||
| 15 | my $td = new TestDriver('page-labels'); | 15 | my $td = new TestDriver('page-labels'); |
| 16 | 16 | ||
| 17 | -my $n_tests = 4; | 17 | +my $n_tests = 5; |
| 18 | 18 | ||
| 19 | $td->runtest("complex page labels", | 19 | $td->runtest("complex page labels", |
| 20 | {$td->COMMAND => "test_driver 47 page-labels-num-tree.pdf"}, | 20 | {$td->COMMAND => "test_driver 47 page-labels-num-tree.pdf"}, |
| @@ -38,6 +38,11 @@ $td->runtest("damaged page labels", | @@ -38,6 +38,11 @@ $td->runtest("damaged page labels", | ||
| 38 | {$td->FILE => "page-labels-num-tree-damaged.out", $td->EXIT_STATUS => 0}, | 38 | {$td->FILE => "page-labels-num-tree-damaged.out", $td->EXIT_STATUS => 0}, |
| 39 | $td->NORMALIZE_NEWLINES); | 39 | $td->NORMALIZE_NEWLINES); |
| 40 | 40 | ||
| 41 | +$td->runtest("damaged page labels --check", | ||
| 42 | + {$td->COMMAND => "qpdf --check page-labels-num-tree-damaged.pdf"}, | ||
| 43 | + {$td->FILE => "page-labels-num-tree-damaged-check.out", $td->EXIT_STATUS => 3}, | ||
| 44 | + $td->NORMALIZE_NEWLINES); | ||
| 45 | + | ||
| 41 | # --set-page-labels | 46 | # --set-page-labels |
| 42 | my @errors = ( | 47 | my @errors = ( |
| 43 | ["quack", ".*page label spec must be.*"], | 48 | ["quack", ".*page label spec must be.*"], |
qpdf/qtest/qpdf/outlines-with-loop-check.out
0 → 100644
| 1 | +checking outlines-with-loop.pdf | ||
| 2 | +PDF Version: 1.3 | ||
| 3 | +File is not encrypted | ||
| 4 | +File is not linearized | ||
| 5 | +WARNING: outlines-with-loop.pdf, object 4 0 at offset 637: Loop detected loop in /Outlines tree | ||
| 6 | +WARNING: outlines-with-loop.pdf, object 5 0 at offset 855: Loop detected loop in /Outlines tree | ||
| 7 | +qpdf: operation succeeded with warnings |
qpdf/qtest/qpdf/outlines-with-loop.out
| 1 | +WARNING: outlines-with-loop.pdf, object 4 0 at offset 637: Loop detected loop in /Outlines tree | ||
| 2 | +WARNING: outlines-with-loop.pdf, object 5 0 at offset 855: Loop detected loop in /Outlines tree | ||
| 1 | page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ] | 3 | page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ] |
| 2 | page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ] | 4 | page 5: Potato 1 -> 5: /XYZ null null null -> [ 11 0 R /XYZ null null null ] |
| 3 | page 11: Mern 1.1 -> 11: /Fit -> [ 17 0 R /Fit ] | 5 | page 11: Mern 1.1 -> 11: /Fit -> [ 17 0 R /Fit ] |
qpdf/qtest/qpdf/page-labels-num-tree-damaged-check.out
0 → 100644
| 1 | +checking page-labels-num-tree-damaged.pdf | ||
| 2 | +PDF Version: 1.3 | ||
| 3 | +File is not encrypted | ||
| 4 | +File is not linearized | ||
| 5 | +WARNING: page-labels-num-tree-damaged.pdf (Name/Number tree node (object 2)): attempting to repair after error: page-labels-num-tree-damaged.pdf (Name/Number tree node (object 2)): keys are not sorted in validate | ||
| 6 | +WARNING: page-labels-num-tree-damaged.pdf (Name/Number tree node (object 37)): item 1 is invalid | ||
| 7 | +qpdf: operation succeeded with warnings |
qpdf/test_driver.cc
| @@ -1802,7 +1802,7 @@ static void | @@ -1802,7 +1802,7 @@ static void | ||
| 1802 | test_47(QPDF& pdf, char const* arg2) | 1802 | test_47(QPDF& pdf, char const* arg2) |
| 1803 | { | 1803 | { |
| 1804 | // Test page labels. | 1804 | // Test page labels. |
| 1805 | - QPDFPageLabelDocumentHelper pldh(pdf); | 1805 | + auto& pldh = QPDFPageLabelDocumentHelper::get(pdf); |
| 1806 | long long npages = pdf.getRoot().getKey("/Pages").getKey("/Count").getIntValue(); | 1806 | long long npages = pdf.getRoot().getKey("/Pages").getKey("/Count").getIntValue(); |
| 1807 | std::vector<QPDFObjectHandle> labels; | 1807 | std::vector<QPDFObjectHandle> labels; |
| 1808 | pldh.getLabelsForPageRange(0, npages - 1, 1, labels); | 1808 | pldh.getLabelsForPageRange(0, npages - 1, 1, labels); |
| @@ -2624,7 +2624,7 @@ test_76(QPDF& pdf, char const* arg2) | @@ -2624,7 +2624,7 @@ test_76(QPDF& pdf, char const* arg2) | ||
| 2624 | { | 2624 | { |
| 2625 | // Embedded files. arg2 is a file to attach. Hard-code the | 2625 | // Embedded files. arg2 is a file to attach. Hard-code the |
| 2626 | // mime type and file name for test purposes. | 2626 | // mime type and file name for test purposes. |
| 2627 | - QPDFEmbeddedFileDocumentHelper efdh(pdf); | 2627 | + auto &efdh = QPDFEmbeddedFileDocumentHelper::get(pdf); |
| 2628 | auto fs1 = QPDFFileSpecObjectHelper::createFileSpec(pdf, "att1.txt", arg2); | 2628 | auto fs1 = QPDFFileSpecObjectHelper::createFileSpec(pdf, "att1.txt", arg2); |
| 2629 | fs1.setDescription("some text"); | 2629 | fs1.setDescription("some text"); |
| 2630 | auto efs1 = QPDFEFStreamObjectHelper(fs1.getEmbeddedFileStream()); | 2630 | auto efs1 = QPDFEFStreamObjectHelper(fs1.getEmbeddedFileStream()); |