diff --git a/examples/pdf-bookmarks.cc b/examples/pdf-bookmarks.cc index 665ac0f..dc0c278 100644 --- a/examples/pdf-bookmarks.cc +++ b/examples/pdf-bookmarks.cc @@ -177,7 +177,7 @@ main(int argc, char* argv[]) QPDF qpdf; qpdf.processFile(filename, password); - QPDFOutlineDocumentHelper odh(qpdf); + auto& odh = QPDFOutlineDocumentHelper::get(qpdf); if (odh.hasOutlines()) { std::vector numbers; if (show_targets) { diff --git a/fuzz/qpdf_outlines_fuzzer.cc b/fuzz/qpdf_outlines_fuzzer.cc index 8efca69..82d0d8c 100644 --- a/fuzz/qpdf_outlines_fuzzer.cc +++ b/fuzz/qpdf_outlines_fuzzer.cc @@ -49,7 +49,7 @@ FuzzHelper::testOutlines() { std::shared_ptr q = getQpdf(); std::list> queue; - QPDFOutlineDocumentHelper odh(*q); + auto& odh = QPDFOutlineDocumentHelper::get(*q); queue.push_back(odh.getTopLevelOutlines()); while (!queue.empty()) { for (auto& ol: *(queue.begin())) { diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 469a492..b8b5e16 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -64,6 +64,7 @@ class QPDFLogger; class QPDFParser; class QPDFAcroFormDocumentHelper; class QPDFEmbeddedFileDocumentHelper; +class QPDFOutlineDocumentHelper; class QPDFPageLabelDocumentHelper; class QPDF @@ -802,6 +803,7 @@ class QPDF inline bool reconstructed_xref() const; inline QPDFAcroFormDocumentHelper& acroform(); inline QPDFEmbeddedFileDocumentHelper& embedded_files(); + inline QPDFOutlineDocumentHelper& outlines(); inline QPDFPageLabelDocumentHelper& page_labels(); // For testing only -- do not add to DLL diff --git a/include/qpdf/QPDFOutlineDocumentHelper.hh b/include/qpdf/QPDFOutlineDocumentHelper.hh index 5dc639a..d6f3ecc 100644 --- a/include/qpdf/QPDFOutlineDocumentHelper.hh +++ b/include/qpdf/QPDFOutlineDocumentHelper.hh @@ -38,6 +38,21 @@ class QPDFOutlineDocumentHelper: 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 QPDFOutlineDocumentHelper& get(QPDF& qpdf); + + // Re-validate the Outlines structure. This is useful if you have modified the structure of the + // Outlines 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 QPDFOutlineDocumentHelper(QPDF&); diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index c8a7ae1..33a2c3e 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -1051,7 +1050,7 @@ QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf) bool first_page = true; JSON::writeArrayOpen(p, first_page, 2); auto& pldh = pdf.page_labels(); - QPDFOutlineDocumentHelper odh(pdf); + auto& odh = pdf.outlines(); int pageno = -1; for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { ++pageno; @@ -1168,8 +1167,7 @@ QPDFJob::doJSONOutlines(Pipeline* p, bool& first, QPDF& pdf) } JSON j_outlines = JSON::makeArray(); - QPDFOutlineDocumentHelper odh(pdf); - addOutlinesToJson(odh.getTopLevelOutlines(), j_outlines, page_numbers); + addOutlinesToJson(pdf.outlines().getTopLevelOutlines(), j_outlines, page_numbers); JSON::writeDictionaryItem(p, first, "outlines", j_outlines, 1); } diff --git a/libqpdf/QPDFOutlineDocumentHelper.cc b/libqpdf/QPDFOutlineDocumentHelper.cc index bcba309..55cd9ee 100644 --- a/libqpdf/QPDFOutlineDocumentHelper.cc +++ b/libqpdf/QPDFOutlineDocumentHelper.cc @@ -1,6 +1,7 @@ #include #include +#include #include class QPDFOutlineDocumentHelper::Members @@ -27,6 +28,21 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) : QPDFDocumentHelper(qpdf), m(std::make_shared()) { + validate(); +} + +QPDFOutlineDocumentHelper& +QPDFOutlineDocumentHelper::get(QPDF& qpdf) +{ + return qpdf.outlines(); +} + +void +QPDFOutlineDocumentHelper::validate(bool repair) +{ + m->outlines.clear(); + m->names_dest = nullptr; + QPDFObjectHandle root = qpdf.getRoot(); if (!root.hasKey("/Outlines")) { return; diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index 833efb1..2f22b7f 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -556,6 +557,7 @@ class QPDF::Members // Document Helpers; std::unique_ptr acroform; std::unique_ptr embedded_files; + std::unique_ptr outlines; std::unique_ptr page_labels; }; @@ -597,6 +599,15 @@ QPDF::embedded_files() return *m->embedded_files; } +inline QPDFOutlineDocumentHelper& +QPDF::outlines() +{ + if (!m->outlines) { + m->outlines = std::make_unique(*this); + } + return *m->outlines; +} + inline QPDFPageLabelDocumentHelper& QPDF::page_labels() {