diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index fe0ce8f..245ed18 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -63,6 +63,7 @@ class BufferInputSource; class QPDFLogger; class QPDFParser; class QPDFAcroFormDocumentHelper; +class QPDFPageLabelDocumentHelper; class QPDF { @@ -799,6 +800,7 @@ class QPDF inline bool reconstructed_xref() const; inline QPDFAcroFormDocumentHelper& acroform(); + inline QPDFPageLabelDocumentHelper& page_labels(); // For testing only -- do not add to DLL static bool test_json_validators(); diff --git a/include/qpdf/QPDFPageLabelDocumentHelper.hh b/include/qpdf/QPDFPageLabelDocumentHelper.hh index c5a9c3f..d04c6ce 100644 --- a/include/qpdf/QPDFPageLabelDocumentHelper.hh +++ b/include/qpdf/QPDFPageLabelDocumentHelper.hh @@ -22,11 +22,10 @@ #include +#include #include -#include -#include -#include +#include // Page labels are discussed in the PDF spec (ISO-32000) in section 12.4.2. // @@ -42,6 +41,21 @@ class QPDFPageLabelDocumentHelper: 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 PageLabels structure, which can be expensive. + QPDF_DLL + static QPDFPageLabelDocumentHelper& get(QPDF& qpdf); + + // Re-validate the PageLabels structure. This is useful if you have modified the structure of + // the PageLabels dictionary in a way that could have invalidated the structure. + // + // 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 QPDFPageLabelDocumentHelper(QPDF&); diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index c9e91a1..3fe7dc2 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -1053,7 +1053,7 @@ QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf) JSON::writeDictionaryKey(p, first, "pages", 1); bool first_page = true; JSON::writeArrayOpen(p, first_page, 2); - QPDFPageLabelDocumentHelper pldh(pdf); + auto& pldh = pdf.page_labels(); QPDFOutlineDocumentHelper odh(pdf); int pageno = -1; for (auto& ph: QPDFPageDocumentHelper(pdf).getAllPages()) { @@ -1116,7 +1116,7 @@ void QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf) { JSON j_labels = JSON::makeArray(); - QPDFPageLabelDocumentHelper pldh(pdf); + auto& pldh = pdf.page_labels(); long long npages = QIntC::to_longlong(QPDFPageDocumentHelper(pdf).getAllPages().size()); if (pldh.hasPageLabels()) { std::vector labels; @@ -2548,7 +2548,7 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector>& page_hea cis = page_spec_cfis[page_data.filename]; cis->stayOpen(true); } - QPDFPageLabelDocumentHelper pldh(*page_data.qpdf); + auto& pldh = page_data.qpdf->page_labels(); auto& other_afdh = page_data.qpdf->acroform(); if (pldh.hasPageLabels()) { any_page_labels = true; @@ -2992,7 +2992,7 @@ QPDFJob::doSplitPages(QPDF& pdf) QPDFPageDocumentHelper dh(pdf); dh.removeUnreferencedResources(); } - QPDFPageLabelDocumentHelper pldh(pdf); + auto& pldh = pdf.page_labels(); auto& afdh = pdf.acroform(); std::vector const& pages = pdf.getAllPages(); size_t pageno_len = std::to_string(pages.size()).length(); diff --git a/libqpdf/QPDFPageLabelDocumentHelper.cc b/libqpdf/QPDFPageLabelDocumentHelper.cc index 878c54e..0f32a8b 100644 --- a/libqpdf/QPDFPageLabelDocumentHelper.cc +++ b/libqpdf/QPDFPageLabelDocumentHelper.cc @@ -1,6 +1,10 @@ #include +#include #include +#include + +using namespace qpdf; class QPDFPageLabelDocumentHelper::Members { @@ -16,14 +20,23 @@ QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF& qpdf) : QPDFDocumentHelper(qpdf), m(std::make_shared()) { - QPDFObjectHandle root = qpdf.getRoot(); - if (root.hasKey("/PageLabels")) { + validate(); +} + +QPDFPageLabelDocumentHelper& +QPDFPageLabelDocumentHelper::get(QPDF& qpdf) +{ + return qpdf.page_labels(); +} + +void +QPDFPageLabelDocumentHelper::validate(bool repair) +{ + m->labels = nullptr; + if (Dictionary labels = qpdf.getRoot()["/PageLabels"]) { m->labels = std::make_unique( - root.getKey("/PageLabels"), - this->qpdf, - [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, - true); - m->labels->validate(); + labels, qpdf, [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, true); + m->labels->validate(repair); } } diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index 19d9e3c..60a9133 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -5,6 +5,7 @@ #include #include +#include #include using namespace qpdf; @@ -553,6 +554,7 @@ class QPDF::Members // Document Helpers; std::unique_ptr acroform; + std::unique_ptr page_labels; }; // JobSetter class is restricted to QPDFJob. @@ -584,4 +586,13 @@ QPDF::acroform() return *m->acroform; } +inline QPDFPageLabelDocumentHelper& +QPDF::page_labels() +{ + if (!m->page_labels) { + m->page_labels = std::make_unique(*this); + } + return *m->page_labels; +} + #endif // QPDF_PRIVATE_HH diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index b2cb985..ffc790a 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -1802,7 +1802,7 @@ static void test_47(QPDF& pdf, char const* arg2) { // Test page labels. - QPDFPageLabelDocumentHelper pldh(pdf); + auto& pldh = QPDFPageLabelDocumentHelper::get(pdf); long long npages = pdf.getRoot().getKey("/Pages").getKey("/Count").getIntValue(); std::vector labels; pldh.getLabelsForPageRange(0, npages - 1, 1, labels);