diff --git a/examples/pdf-attach-file.cc b/examples/pdf-attach-file.cc index 6f3659b..ded5ce2 100644 --- a/examples/pdf-attach-file.cc +++ b/examples/pdf-attach-file.cc @@ -79,7 +79,7 @@ process( } // Add the embedded file at the document level as an attachment. - auto efdh = QPDFEmbeddedFileDocumentHelper(q); + auto& efdh = QPDFEmbeddedFileDocumentHelper::get(q); efdh.replaceEmbeddedFile(key, fs); // Create a file attachment annotation. diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 245ed18..469a492 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -63,6 +63,7 @@ class BufferInputSource; class QPDFLogger; class QPDFParser; class QPDFAcroFormDocumentHelper; +class QPDFEmbeddedFileDocumentHelper; class QPDFPageLabelDocumentHelper; class QPDF @@ -800,6 +801,7 @@ class QPDF inline bool reconstructed_xref() const; inline QPDFAcroFormDocumentHelper& acroform(); + inline QPDFEmbeddedFileDocumentHelper& embedded_files(); inline QPDFPageLabelDocumentHelper& page_labels(); // For testing only -- do not add to DLL diff --git a/include/qpdf/QPDFEmbeddedFileDocumentHelper.hh b/include/qpdf/QPDFEmbeddedFileDocumentHelper.hh index 1a0f606..9db3674 100644 --- a/include/qpdf/QPDFEmbeddedFileDocumentHelper.hh +++ b/include/qpdf/QPDFEmbeddedFileDocumentHelper.hh @@ -36,6 +36,21 @@ class QPDFEmbeddedFileDocumentHelper: 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 EmbeddedFiles structure, which can be expensive. + QPDF_DLL + static QPDFEmbeddedFileDocumentHelper& get(QPDF& qpdf); + + // Re-validate the EmbeddedFiles structure. This is useful if you have modified the structure of + // the EmbeddedFiles dictionary 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 QPDFEmbeddedFileDocumentHelper(QPDF&); diff --git a/libqpdf/QPDFEmbeddedFileDocumentHelper.cc b/libqpdf/QPDFEmbeddedFileDocumentHelper.cc index 552a389..3b95279 100644 --- a/libqpdf/QPDFEmbeddedFileDocumentHelper.cc +++ b/libqpdf/QPDFEmbeddedFileDocumentHelper.cc @@ -1,5 +1,8 @@ #include +#include +#include + // File attachments are stored in the /EmbeddedFiles (name tree) key of the /Names dictionary from // the document catalog. Each entry points to a /FileSpec, which in turn points to one more Embedded // 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) : QPDFDocumentHelper(qpdf), m(std::make_shared()) { + validate(); +} + +QPDFEmbeddedFileDocumentHelper& +QPDFEmbeddedFileDocumentHelper::get(QPDF& qpdf) +{ + return qpdf.embedded_files(); +} + +void +QPDFEmbeddedFileDocumentHelper::validate(bool repair) +{ + m->embedded_files.reset(); auto names = qpdf.getRoot().getKey("/Names"); if (names.isDictionary()) { auto embedded_files = names.getKey("/EmbeddedFiles"); @@ -53,7 +69,7 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) : qpdf, [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, true); - m->embedded_files->validate(); + m->embedded_files->validate(repair); } } } diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index 3fe7dc2..c8a7ae1 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -13,15 +13,12 @@ #include #include #include -#include #include -#include #include #include #include #include #include -#include #include #include #include @@ -894,7 +891,7 @@ QPDFJob::doShowPages(QPDF& pdf) void QPDFJob::doListAttachments(QPDF& pdf) { - QPDFEmbeddedFileDocumentHelper efdh(pdf); + auto& efdh = pdf.embedded_files(); if (efdh.hasEmbeddedFiles()) { for (auto const& i: efdh.getEmbeddedFiles()) { std::string const& key = i.first; @@ -934,7 +931,7 @@ QPDFJob::doListAttachments(QPDF& pdf) void QPDFJob::doShowAttachment(QPDF& pdf) { - QPDFEmbeddedFileDocumentHelper efdh(pdf); + auto& efdh = pdf.embedded_files(); auto fs = efdh.getEmbeddedFile(m->attachment_to_show); if (!fs) { throw std::runtime_error("attachment " + m->attachment_to_show + " not found"); @@ -1321,7 +1318,7 @@ QPDFJob::doJSONAttachments(Pipeline* p, bool& first, QPDF& pdf) }; JSON j_attachments = JSON::makeDictionary(); - QPDFEmbeddedFileDocumentHelper efdh(pdf); + auto& efdh = pdf.embedded_files(); for (auto const& iter: efdh.getEmbeddedFiles()) { std::string const& key = iter.first; auto fsoh = iter.second; @@ -2073,7 +2070,7 @@ void QPDFJob::addAttachments(QPDF& pdf) { maybe_set_pagemode(pdf, "/UseAttachments"); - QPDFEmbeddedFileDocumentHelper efdh(pdf); + auto& efdh = pdf.embedded_files(); std::vector duplicated_keys; for (auto const& to_add: m->attachments_to_add) { if ((!to_add.replace) && efdh.getEmbeddedFile(to_add.key)) { @@ -2117,7 +2114,7 @@ void QPDFJob::copyAttachments(QPDF& pdf) { maybe_set_pagemode(pdf, "/UseAttachments"); - QPDFEmbeddedFileDocumentHelper efdh(pdf); + auto& efdh = pdf.embedded_files(); std::vector duplicates; for (auto const& to_copy: m->attachments_to_copy) { doIfVerbose([&](Pipeline& v, std::string const& prefix) { @@ -2125,7 +2122,7 @@ QPDFJob::copyAttachments(QPDF& pdf) }); std::unique_ptr other; processFile(other, to_copy.path.c_str(), to_copy.password.c_str(), false, false); - QPDFEmbeddedFileDocumentHelper other_efdh(*other); + auto& other_efdh = other->embedded_files(); auto other_attachments = other_efdh.getEmbeddedFiles(); for (auto const& iter: other_attachments) { std::string new_key = to_copy.prefix + iter.first; @@ -2259,7 +2256,7 @@ QPDFJob::handleTransformations(QPDF& pdf) pdf.getRoot().replaceKey("/PageLabels", page_labels); } if (!m->attachments_to_remove.empty()) { - QPDFEmbeddedFileDocumentHelper efdh(pdf); + auto& efdh = pdf.embedded_files(); for (auto const& key: m->attachments_to_remove) { if (efdh.removeEmbeddedFile(key)) { doIfVerbose([&](Pipeline& v, std::string const& prefix) { diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index 60a9133..833efb1 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -554,6 +555,7 @@ class QPDF::Members // Document Helpers; std::unique_ptr acroform; + std::unique_ptr embedded_files; std::unique_ptr page_labels; }; @@ -586,6 +588,15 @@ QPDF::acroform() return *m->acroform; } +inline QPDFEmbeddedFileDocumentHelper& +QPDF::embedded_files() +{ + if (!m->embedded_files) { + m->embedded_files = std::make_unique(*this); + } + return *m->embedded_files; +} + inline QPDFPageLabelDocumentHelper& QPDF::page_labels() { diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index ffc790a..09dcd7a 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -2624,7 +2624,7 @@ test_76(QPDF& pdf, char const* arg2) { // Embedded files. arg2 is a file to attach. Hard-code the // mime type and file name for test purposes. - QPDFEmbeddedFileDocumentHelper efdh(pdf); + auto &efdh = QPDFEmbeddedFileDocumentHelper::get(pdf); auto fs1 = QPDFFileSpecObjectHelper::createFileSpec(pdf, "att1.txt", arg2); fs1.setDescription("some text"); auto efs1 = QPDFEFStreamObjectHelper(fs1.getEmbeddedFileStream());