Commit 4ba6377f00551d34b8a77b201955e1cefd1ef3db

Authored by m-holger
Committed by GitHub
2 parents 0c8af4a3 20ef3173

Merge pull request #1557 from m-holger/qpdf_hh

Remove implementation detail from QPDF header file
include/qpdf/QPDF.hh
... ... @@ -747,20 +747,7 @@ class QPDF
747 747 class ResolveRecorder;
748 748 class JSONReactor;
749 749  
750   - void stopOnError(std::string const& message);
751   - inline void
752   - no_ci_stop_if(bool condition, std::string const& message, std::string const& context = {});
753 750 void removeObject(QPDFObjGen og);
754   - static QPDFExc damagedPDF(
755   - InputSource& input,
756   - std::string const& object,
757   - qpdf_offset_t offset,
758   - std::string const& message);
759   - QPDFExc damagedPDF(InputSource& input, qpdf_offset_t offset, std::string const& message);
760   - QPDFExc damagedPDF(std::string const& object, qpdf_offset_t offset, std::string const& message);
761   - QPDFExc damagedPDF(std::string const& object, std::string const& message);
762   - QPDFExc damagedPDF(qpdf_offset_t offset, std::string const& message);
763   - QPDFExc damagedPDF(std::string const& message);
764 751  
765 752 // Calls finish() on the pipeline when done but does not delete it
766 753 bool pipeStreamData(
... ...
include/qpdf/QPDFPageDocumentHelper.hh
... ... @@ -120,13 +120,6 @@ class QPDFPageDocumentHelper: public QPDFDocumentHelper
120 120 void flattenAnnotations(int required_flags = 0, int forbidden_flags = an_invisible | an_hidden);
121 121  
122 122 private:
123   - void flattenAnnotationsForPage(
124   - QPDFPageObjectHelper& page,
125   - QPDFObjectHandle& resources,
126   - QPDFAcroFormDocumentHelper& afdh,
127   - int required_flags,
128   - int forbidden_flags);
129   -
130 123 class Members;
131 124  
132 125 std::shared_ptr<Members> m;
... ...
libqpdf/CMakeLists.txt
... ... @@ -76,7 +76,6 @@ set(libqpdf_SOURCES
76 76 QPDFObjectHelper.cc
77 77 QPDFOutlineDocumentHelper.cc
78 78 QPDFOutlineObjectHelper.cc
79   - QPDFPageDocumentHelper.cc
80 79 QPDFPageLabelDocumentHelper.cc
81 80 QPDFPageObjectHelper.cc
82 81 QPDFParser.cc
... ... @@ -94,7 +93,6 @@ set(libqpdf_SOURCES
94 93 QPDF_json.cc
95 94 QPDF_linearization.cc
96 95 QPDF_objects.cc
97   - QPDF_optimization.cc
98 96 QPDF_pages.cc
99 97 QTC.cc
100 98 QUtil.cc
... ...
libqpdf/QPDF.cc
... ... @@ -27,7 +27,9 @@
27 27 using namespace qpdf;
28 28 using namespace std::literals;
29 29  
30   -using Objects = QPDF::Doc::Objects;
  30 +using Doc = QPDF::Doc;
  31 +using Common = Doc::Common;
  32 +using Objects = Doc::Objects;
31 33 using Foreign = Objects::Foreign;
32 34 using Streams = Objects::Streams;
33 35  
... ... @@ -126,10 +128,11 @@ QPDF::QPDFVersion()
126 128 }
127 129  
128 130 QPDF::Members::Members(QPDF& qpdf) :
129   - doc(qpdf, *this),
130   - lin(doc.linearization()),
131   - objects(doc.objects()),
132   - pages(doc.pages()),
  131 + Doc(qpdf, this),
  132 + c(qpdf, this),
  133 + lin(*this),
  134 + objects(*this),
  135 + pages(*this),
133 136 log(QPDFLogger::defaultLogger()),
134 137 file(std::make_shared<InvalidInputSource>()),
135 138 encp(std::make_shared<EncryptionParameters>())
... ... @@ -363,6 +366,12 @@ QPDF::findHeader()
363 366 void
364 367 QPDF::warn(QPDFExc const& e)
365 368 {
  369 + m->c.warn(e);
  370 +}
  371 +
  372 +void
  373 +Common::warn(QPDFExc const& e)
  374 +{
366 375 if (m->max_warnings > 0 && m->warnings.size() >= m->max_warnings) {
367 376 stopOnError("Too many warnings - file is too badly damaged");
368 377 }
... ... @@ -379,7 +388,17 @@ QPDF::warn(
379 388 qpdf_offset_t offset,
380 389 std::string const& message)
381 390 {
382   - warn(QPDFExc(error_code, getFilename(), object, offset, message));
  391 + m->c.warn(QPDFExc(error_code, getFilename(), object, offset, message));
  392 +}
  393 +
  394 +void
  395 +Common::warn(
  396 + qpdf_error_code_e error_code,
  397 + std::string const& object,
  398 + qpdf_offset_t offset,
  399 + std::string const& message)
  400 +{
  401 + warn(QPDFExc(error_code, qpdf.getFilename(), object, offset, message));
383 402 }
384 403  
385 404 QPDFObjectHandle
... ... @@ -514,7 +533,7 @@ Objects::Foreign::Copier::copied(QPDFObjectHandle const&amp; foreign)
514 533  
515 534 auto og = foreign.getObjGen();
516 535 if (!object_map.contains(og)) {
517   - qpdf.warn(qpdf.damagedPDF(
  536 + warn(damagedPDF(
518 537 foreign.qpdf()->getFilename() + " object " + og.unparse(' '),
519 538 foreign.offset(),
520 539 "unexpected reference to /Pages object while copying foreign object; replacing with "
... ... @@ -692,12 +711,12 @@ QPDF::getRoot()
692 711 {
693 712 QPDFObjectHandle root = m->trailer.getKey("/Root");
694 713 if (!root.isDictionary()) {
695   - throw damagedPDF("", -1, "unable to find /Root dictionary");
  714 + throw m->c.damagedPDF("", -1, "unable to find /Root dictionary");
696 715 } else if (
697 716 // Check_mode is an interim solution to request #810 pending a more comprehensive review of
698 717 // the approach to more extensive checks and warning levels.
699 718 m->check_mode && !root.getKey("/Type").isNameAndEquals("/Catalog")) {
700   - warn(damagedPDF("", -1, "catalog /Type entry missing or invalid"));
  719 + warn(m->c.damagedPDF("", -1, "catalog /Type entry missing or invalid"));
701 720 root.replaceKey("/Type", "/Catalog"_qpdf);
702 721 }
703 722 return root;
... ... @@ -743,7 +762,7 @@ QPDF::pipeStreamData(
743 762 try {
744 763 auto buf = file->read(length, offset);
745 764 if (buf.size() != length) {
746   - throw damagedPDF(
  765 + throw qpdf_for_warning.m->c.damagedPDF(
747 766 *file, "", offset + toO(buf.size()), "unexpected EOF reading stream data");
748 767 }
749 768 pipeline->write(buf.data(), length);
... ... @@ -759,7 +778,7 @@ QPDF::pipeStreamData(
759 778 QTC::TC("qpdf", "QPDF decoding error warning");
760 779 qpdf_for_warning.warn(
761 780 // line-break
762   - damagedPDF(
  781 + qpdf_for_warning.m->c.damagedPDF(
763 782 *file,
764 783 "",
765 784 file->getLastOffset(),
... ... @@ -768,7 +787,7 @@ QPDF::pipeStreamData(
768 787 if (will_retry) {
769 788 qpdf_for_warning.warn(
770 789 // line-break
771   - damagedPDF(
  790 + qpdf_for_warning.m->c.damagedPDF(
772 791 *file,
773 792 "",
774 793 file->getLastOffset(),
... ... @@ -814,14 +833,14 @@ QPDF::pipeStreamData(
814 833 // Throw a generic exception when we lack context for something more specific. New code should not
815 834 // use this.
816 835 void
817   -QPDF::stopOnError(std::string const& message)
  836 +Common::stopOnError(std::string const& message)
818 837 {
819 838 throw damagedPDF("", message);
820 839 }
821 840  
822 841 // Return an exception of type qpdf_e_damaged_pdf.
823 842 QPDFExc
824   -QPDF::damagedPDF(
  843 +Common::damagedPDF(
825 844 InputSource& input, std::string const& object, qpdf_offset_t offset, std::string const& message)
826 845 {
827 846 return {qpdf_e_damaged_pdf, input.getName(), object, offset, message, true};
... ... @@ -830,14 +849,15 @@ QPDF::damagedPDF(
830 849 // Return an exception of type qpdf_e_damaged_pdf. The object is taken from
831 850 // m->last_object_description.
832 851 QPDFExc
833   -QPDF::damagedPDF(InputSource& input, qpdf_offset_t offset, std::string const& message)
  852 +Common::damagedPDF(InputSource& input, qpdf_offset_t offset, std::string const& message) const
834 853 {
835 854 return damagedPDF(input, m->last_object_description, offset, message);
836 855 }
837 856  
838 857 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file.
839 858 QPDFExc
840   -QPDF::damagedPDF(std::string const& object, qpdf_offset_t offset, std::string const& message)
  859 +Common::damagedPDF(
  860 + std::string const& object, qpdf_offset_t offset, std::string const& message) const
841 861 {
842 862 return {qpdf_e_damaged_pdf, m->file->getName(), object, offset, message, true};
843 863 }
... ... @@ -845,7 +865,7 @@ QPDF::damagedPDF(std::string const&amp; object, qpdf_offset_t offset, std::string co
845 865 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file and the
846 866 // offset from .m->file->getLastOffset().
847 867 QPDFExc
848   -QPDF::damagedPDF(std::string const& object, std::string const& message)
  868 +Common::damagedPDF(std::string const& object, std::string const& message) const
849 869 {
850 870 return damagedPDF(object, m->file->getLastOffset(), message);
851 871 }
... ... @@ -853,7 +873,7 @@ QPDF::damagedPDF(std::string const&amp; object, std::string const&amp; message)
853 873 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file and the object
854 874 // from .m->last_object_description.
855 875 QPDFExc
856   -QPDF::damagedPDF(qpdf_offset_t offset, std::string const& message)
  876 +Common::damagedPDF(qpdf_offset_t offset, std::string const& message) const
857 877 {
858 878 return damagedPDF(m->last_object_description, offset, message);
859 879 }
... ... @@ -861,7 +881,7 @@ QPDF::damagedPDF(qpdf_offset_t offset, std::string const&amp; message)
861 881 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file, the object
862 882 // from m->last_object_description and the offset from m->file->getLastOffset().
863 883 QPDFExc
864   -QPDF::damagedPDF(std::string const& message)
  884 +Common::damagedPDF(std::string const& message) const
865 885 {
866 886 return damagedPDF(m->last_object_description, m->file->getLastOffset(), message);
867 887 }
... ... @@ -869,13 +889,13 @@ QPDF::damagedPDF(std::string const&amp; message)
869 889 bool
870 890 QPDF::everCalledGetAllPages() const
871 891 {
872   - return m->ever_called_get_all_pages;
  892 + return m->pages.ever_called_get_all_pages();
873 893 }
874 894  
875 895 bool
876 896 QPDF::everPushedInheritedAttributesToPages() const
877 897 {
878   - return m->ever_pushed_inherited_attributes_to_pages;
  898 + return m->pages.ever_pushed_inherited_attributes_to_pages();
879 899 }
880 900  
881 901 void
... ...
libqpdf/QPDFJob.cc
... ... @@ -30,8 +30,11 @@
30 30  
31 31 using namespace qpdf;
32 32  
  33 +using Doc = QPDF::Doc;
  34 +using Pages = Doc::Pages;
  35 +
33 36 // JobSetter class is restricted to QPDFJob.
34   -class QPDF::Doc::JobSetter
  37 +class Doc::JobSetter
35 38 {
36 39 public:
37 40 // Enable enhanced warnings for pdf file checking.
... ... @@ -746,7 +749,7 @@ QPDFJob::doCheck(QPDF&amp; pdf)
746 749 bool okay = true;
747 750 auto& cout = *m->log->getInfo();
748 751 cout << "checking " << m->infile_name() << "\n";
749   - QPDF::Doc::JobSetter::setCheckMode(pdf, true);
  752 + Doc::JobSetter::setCheckMode(pdf, true);
750 753 try {
751 754 int extension_level = pdf.getExtensionLevel();
752 755 cout << "PDF Version: " << pdf.getPDFVersion();
... ... @@ -852,7 +855,7 @@ QPDFJob::doShowPages(QPDF&amp; pdf)
852 855 {
853 856 int pageno = 0;
854 857 auto& cout = *m->log->getInfo();
855   - for (auto& page: pdf.getAllPages()) {
  858 + for (auto& page: pdf.doc().pages()) {
856 859 QPDFPageObjectHelper ph(page);
857 860 ++pageno;
858 861  
... ... @@ -1040,13 +1043,14 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1040 1043 void
1041 1044 QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf)
1042 1045 {
  1046 + auto& doc = pdf.doc();
1043 1047 JSON::writeDictionaryKey(p, first, "pages", 1);
1044 1048 bool first_page = true;
1045 1049 JSON::writeArrayOpen(p, first_page, 2);
1046   - auto& pldh = pdf.doc().page_labels();
1047   - auto& odh = pdf.doc().outlines();
  1050 + auto& pldh = doc.page_labels();
  1051 + auto& odh = doc.outlines();
1048 1052 int pageno = -1;
1049   - for (auto& page: pdf.getAllPages()) {
  1053 + for (auto& page: doc.pages()) {
1050 1054 ++pageno;
1051 1055 JSON j_page = JSON::makeDictionary();
1052 1056 QPDFPageObjectHelper ph(page);
... ... @@ -1105,9 +1109,10 @@ QPDFJob::doJSONPages(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1105 1109 void
1106 1110 QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf)
1107 1111 {
  1112 + auto& doc = pdf.doc();
1108 1113 JSON j_labels = JSON::makeArray();
1109   - auto& pldh = pdf.doc().page_labels();
1110   - long long npages = QIntC::to_longlong(pdf.getAllPages().size());
  1114 + auto& pldh = doc.page_labels();
  1115 + long long npages = QIntC::to_longlong(doc.pages().size());
1111 1116 if (pldh.hasPageLabels()) {
1112 1117 std::vector<QPDFObjectHandle> labels;
1113 1118 pldh.getLabelsForPageRange(0, npages - 1, 0, labels);
... ... @@ -1153,27 +1158,29 @@ QPDFJob::addOutlinesToJson(
1153 1158 void
1154 1159 QPDFJob::doJSONOutlines(Pipeline* p, bool& first, QPDF& pdf)
1155 1160 {
  1161 + auto& doc = pdf.doc();
1156 1162 std::map<QPDFObjGen, int> page_numbers;
1157 1163 int n = 0;
1158   - for (auto const& oh: pdf.getAllPages()) {
  1164 + for (auto const& oh: doc.pages()) {
1159 1165 page_numbers[oh] = ++n;
1160 1166 }
1161 1167  
1162 1168 JSON j_outlines = JSON::makeArray();
1163   - addOutlinesToJson(pdf.doc().outlines().getTopLevelOutlines(), j_outlines, page_numbers);
  1169 + addOutlinesToJson(doc.outlines().getTopLevelOutlines(), j_outlines, page_numbers);
1164 1170 JSON::writeDictionaryItem(p, first, "outlines", j_outlines, 1);
1165 1171 }
1166 1172  
1167 1173 void
1168 1174 QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf)
1169 1175 {
  1176 + auto& doc = pdf.doc();
1170 1177 JSON j_acroform = JSON::makeDictionary();
1171   - auto& afdh = pdf.doc().acroform();
  1178 + auto& afdh = doc.acroform();
1172 1179 j_acroform.addDictionaryMember("hasacroform", JSON::makeBool(afdh.hasAcroForm()));
1173 1180 j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances()));
1174 1181 JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray());
1175 1182 int pagepos1 = 0;
1176   - for (auto const& page: pdf.getAllPages()) {
  1183 + for (auto const& page: doc.pages()) {
1177 1184 ++pagepos1;
1178 1185 for (auto& aoh: afdh.getWidgetAnnotationsForPage({page})) {
1179 1186 QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh);
... ... @@ -1852,7 +1859,7 @@ QPDFJob::validateUnderOverlay(QPDF&amp; pdf, UnderOverlay* uo)
1852 1859 processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false);
1853 1860 try {
1854 1861 uo->to_pagenos =
1855   - QUtil::parse_numrange(uo->to_nr.data(), static_cast<int>(pdf.getAllPages().size()));
  1862 + QUtil::parse_numrange(uo->to_nr.data(), static_cast<int>(pdf.doc().pages().size()));
1856 1863 } catch (std::runtime_error& e) {
1857 1864 throw std::runtime_error(
1858 1865 "parsing numeric range for " + uo->which + " \"to\" pages: " + e.what());
... ... @@ -1861,7 +1868,7 @@ QPDFJob::validateUnderOverlay(QPDF&amp; pdf, UnderOverlay* uo)
1861 1868 if (uo->from_nr.empty()) {
1862 1869 uo->from_nr = uo->repeat_nr;
1863 1870 }
1864   - int uo_npages = static_cast<int>(uo->pdf->getAllPages().size());
  1871 + int uo_npages = static_cast<int>(uo->pdf->doc().pages().size());
1865 1872 uo->from_pagenos = QUtil::parse_numrange(uo->from_nr.data(), uo_npages);
1866 1873 if (!uo->repeat_nr.empty()) {
1867 1874 uo->repeat_pagenos = QUtil::parse_numrange(uo->repeat_nr.data(), uo_npages);
... ... @@ -1887,7 +1894,7 @@ QPDFJob::doUnderOverlayForPage(
1887 1894 }
1888 1895 auto& dest_afdh = dest_page.qpdf()->doc().acroform();
1889 1896  
1890   - auto const& pages = uo.pdf->getAllPages();
  1897 + auto const& pages = uo.pdf->doc().pages().all();
1891 1898 std::string content;
1892 1899 int min_suffix = 1;
1893 1900 QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true);
... ... @@ -1957,7 +1964,7 @@ QPDFJob::handleUnderOverlay(QPDF&amp; pdf)
1957 1964 validateUnderOverlay(pdf, &uo);
1958 1965 }
1959 1966  
1960   - auto const& dest_pages = pdf.getAllPages();
  1967 + auto const& dest_pages = pdf.doc().pages().all();
1961 1968  
1962 1969 // First vector key is 0-based page number. Second is index into the overlay/underlay vector.
1963 1970 // Watch out to not reverse the keys or be off by one.
... ... @@ -2349,14 +2356,15 @@ QPDFJob::Input::initialize(Inputs&amp; in, QPDF* a_qpdf)
2349 2356 {
2350 2357 qpdf = a_qpdf ? a_qpdf : qpdf_p.get();
2351 2358 if (qpdf) {
2352   - orig_pages = qpdf->getAllPages();
  2359 + auto& doc = qpdf->doc();
  2360 + orig_pages = doc.pages().all();
2353 2361 n_pages = static_cast<int>(orig_pages.size());
2354 2362 copied_pages = std::vector<bool>(orig_pages.size(), false);
2355 2363  
2356 2364 if (in.job.m->remove_unreferenced_page_resources != QPDFJob::re_no) {
2357 2365 remove_unreferenced = in.job.shouldRemoveUnreferencedResources(*qpdf);
2358 2366 }
2359   - if (qpdf->doc().page_labels().hasPageLabels()) {
  2367 + if (doc.page_labels().hasPageLabels()) {
2360 2368 in.any_page_labels = true;
2361 2369 }
2362 2370 }
... ... @@ -3001,6 +3009,8 @@ QPDFJob::setWriterOptions(QPDFWriter&amp; w)
3001 3009 void
3002 3010 QPDFJob::doSplitPages(QPDF& pdf)
3003 3011 {
  3012 + auto& doc = pdf.doc();
  3013 +
3004 3014 // Generate output file pattern
3005 3015 std::string before;
3006 3016 std::string after;
... ... @@ -3024,9 +3034,9 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3024 3034 QPDFPageDocumentHelper dh(pdf);
3025 3035 dh.removeUnreferencedResources();
3026 3036 }
3027   - auto& pldh = pdf.doc().page_labels();
3028   - auto& afdh = pdf.doc().acroform();
3029   - std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
  3037 + auto& pldh = doc.page_labels();
  3038 + auto& afdh = doc.acroform();
  3039 + std::vector<QPDFObjectHandle> const& pages = doc.pages().all();
3030 3040 size_t pageno_len = std::to_string(pages.size()).length();
3031 3041 size_t num_pages = pages.size();
3032 3042 for (size_t i = 0; i < num_pages; i += QIntC::to_size(m->split_pages)) {
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -2099,22 +2099,22 @@ bool
2099 2099 QPDFObjectHandle::isPageObject() const
2100 2100 {
2101 2101 // See comments in QPDFObjectHandle.hh.
2102   - if (getOwningQPDF() == nullptr) {
  2102 + if (!qpdf()) {
2103 2103 return false;
2104 2104 }
2105 2105 // getAllPages repairs /Type when traversing the page tree.
2106   - getOwningQPDF()->getAllPages();
  2106 + (void)qpdf()->doc().pages().all();
2107 2107 return isDictionaryOfType("/Page");
2108 2108 }
2109 2109  
2110 2110 bool
2111 2111 QPDFObjectHandle::isPagesObject() const
2112 2112 {
2113   - if (getOwningQPDF() == nullptr) {
  2113 + if (!qpdf()) {
2114 2114 return false;
2115 2115 }
2116 2116 // getAllPages repairs /Type when traversing the page tree.
2117   - getOwningQPDF()->getAllPages();
  2117 + (void)qpdf()->doc().pages().all();
2118 2118 return isDictionaryOfType("/Pages");
2119 2119 }
2120 2120  
... ...
libqpdf/QPDFPageDocumentHelper.cc deleted
1   -#include <qpdf/QPDFPageDocumentHelper.hh>
2   -
3   -#include <qpdf/QPDFAcroFormDocumentHelper.hh>
4   -#include <qpdf/QPDFObjectHandle_private.hh>
5   -#include <qpdf/QPDF_private.hh>
6   -#include <qpdf/QTC.hh>
7   -#include <qpdf/QUtil.hh>
8   -
9   -class QPDFPageDocumentHelper::Members
10   -{
11   -};
12   -
13   -QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) :
14   - QPDFDocumentHelper(qpdf)
15   -{
16   -}
17   -
18   -QPDFPageDocumentHelper&
19   -QPDFPageDocumentHelper::get(QPDF& qpdf)
20   -{
21   - return qpdf.doc().page_dh();
22   -}
23   -
24   -void
25   -QPDFPageDocumentHelper::validate(bool repair)
26   -{
27   -}
28   -
29   -std::vector<QPDFPageObjectHelper>
30   -QPDFPageDocumentHelper::getAllPages()
31   -{
32   - std::vector<QPDFPageObjectHelper> pages;
33   - for (auto const& iter: qpdf.getAllPages()) {
34   - pages.emplace_back(iter);
35   - }
36   - return pages;
37   -}
38   -
39   -void
40   -QPDFPageDocumentHelper::pushInheritedAttributesToPage()
41   -{
42   - qpdf.pushInheritedAttributesToPage();
43   -}
44   -
45   -void
46   -QPDFPageDocumentHelper::removeUnreferencedResources()
47   -{
48   - for (auto& ph: getAllPages()) {
49   - ph.removeUnreferencedResources();
50   - }
51   -}
52   -
53   -void
54   -QPDFPageDocumentHelper::addPage(QPDFPageObjectHelper newpage, bool first)
55   -{
56   - qpdf.addPage(newpage.getObjectHandle(), first);
57   -}
58   -
59   -void
60   -QPDFPageDocumentHelper::addPageAt(
61   - QPDFPageObjectHelper newpage, bool before, QPDFPageObjectHelper refpage)
62   -{
63   - qpdf.addPageAt(newpage.getObjectHandle(), before, refpage.getObjectHandle());
64   -}
65   -
66   -void
67   -QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page)
68   -{
69   - qpdf.removePage(page.getObjectHandle());
70   -}
71   -
72   -void
73   -QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags)
74   -{
75   - auto& afdh = qpdf.doc().acroform();
76   - if (afdh.getNeedAppearances()) {
77   - qpdf.getRoot()
78   - .getKey("/AcroForm")
79   - .warn(
80   - "document does not have updated appearance streams, so form fields "
81   - "will not be flattened");
82   - }
83   - for (auto& ph: getAllPages()) {
84   - QPDFObjectHandle resources = ph.getAttribute("/Resources", true);
85   - if (!resources.isDictionary()) {
86   - // As of #1521, this should be impossible unless a user inserted an invalid page.
87   - resources = ph.getObjectHandle().replaceKeyAndGetNew(
88   - "/Resources", QPDFObjectHandle::newDictionary());
89   - }
90   - flattenAnnotationsForPage(ph, resources, afdh, required_flags, forbidden_flags);
91   - }
92   - if (!afdh.getNeedAppearances()) {
93   - qpdf.getRoot().removeKey("/AcroForm");
94   - }
95   -}
96   -
97   -void
98   -QPDFPageDocumentHelper::flattenAnnotationsForPage(
99   - QPDFPageObjectHelper& page,
100   - QPDFObjectHandle& resources,
101   - QPDFAcroFormDocumentHelper& afdh,
102   - int required_flags,
103   - int forbidden_flags)
104   -{
105   - bool need_appearances = afdh.getNeedAppearances();
106   - std::vector<QPDFAnnotationObjectHelper> annots = page.getAnnotations();
107   - std::vector<QPDFObjectHandle> new_annots;
108   - std::string new_content;
109   - int rotate = 0;
110   - QPDFObjectHandle rotate_obj = page.getObjectHandle().getKey("/Rotate");
111   - if (rotate_obj.isInteger() && rotate_obj.getIntValue()) {
112   - rotate = rotate_obj.getIntValueAsInt();
113   - }
114   - int next_fx = 1;
115   - for (auto& aoh: annots) {
116   - QPDFObjectHandle as = aoh.getAppearanceStream("/N");
117   - bool is_widget = (aoh.getSubtype() == "/Widget");
118   - bool process = true;
119   - if (need_appearances && is_widget) {
120   - QTC::TC("qpdf", "QPDFPageDocumentHelper skip widget need appearances");
121   - process = false;
122   - }
123   - if (process && as.isStream()) {
124   - if (is_widget) {
125   - QTC::TC("qpdf", "QPDFPageDocumentHelper merge DR");
126   - QPDFFormFieldObjectHelper ff = afdh.getFieldForAnnotation(aoh);
127   - QPDFObjectHandle as_resources = as.getDict().getKey("/Resources");
128   - if (as_resources.isIndirect()) {
129   - QTC::TC("qpdf", "QPDFPageDocumentHelper indirect as resources");
130   - as.getDict().replaceKey("/Resources", as_resources.shallowCopy());
131   - as_resources = as.getDict().getKey("/Resources");
132   - }
133   - as_resources.mergeResources(ff.getDefaultResources());
134   - } else {
135   - QTC::TC("qpdf", "QPDFPageDocumentHelper non-widget annotation");
136   - }
137   - std::string name = resources.getUniqueResourceName("/Fxo", next_fx);
138   - std::string content =
139   - aoh.getPageContentForAppearance(name, rotate, required_flags, forbidden_flags);
140   - if (!content.empty()) {
141   - resources.mergeResources("<< /XObject << >> >>"_qpdf);
142   - resources.getKey("/XObject").replaceKey(name, as);
143   - ++next_fx;
144   - }
145   - new_content += content;
146   - } else if (process && !aoh.getAppearanceDictionary().null()) {
147   - // If an annotation has no selected appearance stream, just drop the annotation when
148   - // flattening. This can happen for unchecked checkboxes and radio buttons, popup windows
149   - // associated with comments that aren't visible, and other types of annotations that
150   - // aren't visible. Annotations that have no appearance streams at all, such as Link,
151   - // Popup, and Projection, should be preserved.
152   - QTC::TC("qpdf", "QPDFPageDocumentHelper ignore annotation with no appearance");
153   - } else {
154   - new_annots.push_back(aoh.getObjectHandle());
155   - }
156   - }
157   - if (new_annots.size() != annots.size()) {
158   - QPDFObjectHandle page_oh = page.getObjectHandle();
159   - if (new_annots.empty()) {
160   - QTC::TC("qpdf", "QPDFPageDocumentHelper remove annots");
161   - page_oh.removeKey("/Annots");
162   - } else {
163   - QPDFObjectHandle old_annots = page_oh.getKey("/Annots");
164   - QPDFObjectHandle new_annots_oh = QPDFObjectHandle::newArray(new_annots);
165   - if (old_annots.isIndirect()) {
166   - QTC::TC("qpdf", "QPDFPageDocumentHelper replace indirect annots");
167   - qpdf.replaceObject(old_annots.getObjGen(), new_annots_oh);
168   - } else {
169   - QTC::TC("qpdf", "QPDFPageDocumentHelper replace direct annots");
170   - page_oh.replaceKey("/Annots", new_annots_oh);
171   - }
172   - }
173   - page.addPageContents(qpdf.newStream("q\n"), true);
174   - page.addPageContents(qpdf.newStream("\nQ\n" + new_content), false);
175   - }
176   -}
libqpdf/QPDFWriter.cc
... ... @@ -27,7 +27,8 @@
27 27 using namespace std::literals;
28 28 using namespace qpdf;
29 29  
30   -using Encryption = QPDF::Doc::Encryption;
  30 +using Doc = QPDF::Doc;
  31 +using Encryption = Doc::Encryption;
31 32  
32 33 QPDFWriter::ProgressReporter::~ProgressReporter() // NOLINT (modernize-use-equals-default)
33 34 {
... ... @@ -262,13 +263,13 @@ Pl_stack::Popper::pop()
262 263 }
263 264  
264 265 // Writer class is restricted to QPDFWriter so that only it can call certain methods.
265   -class QPDF::Doc::Writer
  266 +class Doc::Writer: Doc::Common
266 267 {
267 268 friend class QPDFWriter;
268   - Writer(QPDF& pdf) :
269   - pdf(pdf),
270   - lin(pdf.m->lin),
271   - objects(pdf.m->objects)
  269 + Writer(QPDF& qpdf) :
  270 + Common(qpdf, qpdf.doc().m),
  271 + lin(m->lin),
  272 + objects(m->objects)
272 273 {
273 274 }
274 275  
... ... @@ -326,10 +327,9 @@ class QPDF::Doc::Writer
326 327 size_t
327 328 tableSize()
328 329 {
329   - return pdf.m->objects.tableSize();
  330 + return qpdf.m->objects.tableSize();
330 331 }
331 332  
332   - QPDF& pdf;
333 333 QPDF::Doc::Linearization& lin;
334 334 QPDF::Doc::Objects& objects;
335 335 };
... ... @@ -352,7 +352,8 @@ class QPDFWriter::Members: QPDF::Doc::Writer
352 352 QPDF::Doc::Writer(pdf),
353 353 w(w),
354 354 root_og(
355   - pdf.getRoot().getObjGen().isIndirect() ? pdf.getRoot().getObjGen() : QPDFObjGen(-1, 0)),
  355 + qpdf.getRoot().getObjGen().isIndirect() ? qpdf.getRoot().getObjGen()
  356 + : QPDFObjGen(-1, 0)),
356 357 pipeline_stack(pipeline)
357 358 {
358 359 }
... ... @@ -1372,7 +1373,7 @@ QPDFWriter::Members::enqueueObject(QPDFObjectHandle object)
1372 1373 // object to have an owning QPDF that is from another file if a direct QPDFObjectHandle from
1373 1374 // one file was insert into another file without copying. Doing that is safe even if the
1374 1375 // original QPDF gets destroyed, which just disconnects the QPDFObjectHandle from its owner.
1375   - if (object.getOwningQPDF() != &pdf) {
  1376 + if (object.getOwningQPDF() != &qpdf) {
1376 1377 throw std::logic_error(
1377 1378 "QPDFObjectHandle from different QPDF found while writing. Use "
1378 1379 "QPDF::copyForeignObject to add objects from another file.");
... ... @@ -1396,7 +1397,7 @@ QPDFWriter::Members::enqueueObject(QPDFObjectHandle object)
1396 1397 // stream. Object streams always have generation 0.
1397 1398 // Detect loops by storing invalid object ID -1, which will get overwritten later.
1398 1399 o.renumber = -1;
1399   - enqueueObject(pdf.getObject(o.object_stream, 0));
  1400 + enqueueObject(qpdf.getObject(o.object_stream, 0));
1400 1401 } else {
1401 1402 object_queue.emplace_back(object);
1402 1403 o.renumber = next_objid++;
... ... @@ -1907,7 +1908,7 @@ QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object)
1907 1908 // reporting, decrement in pass 1.
1908 1909 indicateProgress(true, false);
1909 1910  
1910   - QPDFObjectHandle obj_to_write = pdf.getObject(og);
  1911 + QPDFObjectHandle obj_to_write = qpdf.getObject(og);
1911 1912 if (obj_to_write.isStream()) {
1912 1913 // This condition occurred in a fuzz input. Ideally we should block it at parse
1913 1914 // time, but it's not clear to me how to construct a case for this.
... ... @@ -2024,7 +2025,7 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde
2024 2025 std::string
2025 2026 QPDFWriter::Members::getOriginalID1()
2026 2027 {
2027   - QPDFObjectHandle trailer = pdf.getTrailer();
  2028 + QPDFObjectHandle trailer = qpdf.getTrailer();
2028 2029 if (trailer.hasKey("/ID")) {
2029 2030 return trailer.getKey("/ID").getArrayItem(0).getStringValue();
2030 2031 } else {
... ... @@ -2042,7 +2043,7 @@ QPDFWriter::Members::generateID(bool encrypted)
2042 2043 return;
2043 2044 }
2044 2045  
2045   - QPDFObjectHandle trailer = pdf.getTrailer();
  2046 + QPDFObjectHandle trailer = qpdf.getTrailer();
2046 2047  
2047 2048 std::string result;
2048 2049  
... ... @@ -2126,7 +2127,6 @@ void
2126 2127 QPDFWriter::Members::initializeSpecialStreams()
2127 2128 {
2128 2129 // Mark all page content streams in case we are filtering or normalizing.
2129   - std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
2130 2130 int num = 0;
2131 2131 for (auto& page: pages) {
2132 2132 page_object_to_seq[page.getObjGen()] = ++num;
... ... @@ -2220,13 +2220,13 @@ QPDFWriter::Members::generateObjectStreams()
2220 2220 ++n_per;
2221 2221 }
2222 2222 unsigned int n = 0;
2223   - int cur_ostream = pdf.newIndirectNull().getObjectID();
  2223 + int cur_ostream = qpdf.newIndirectNull().getObjectID();
2224 2224 for (auto const& item: eligible) {
2225 2225 if (n == n_per) {
2226 2226 n = 0;
2227 2227 // Construct a new null object as the "original" object stream. The rest of the code
2228 2228 // knows that this means we're creating the object stream from scratch.
2229   - cur_ostream = pdf.newIndirectNull().getObjectID();
  2229 + cur_ostream = qpdf.newIndirectNull().getObjectID();
2230 2230 }
2231 2231 auto& o = obj[item];
2232 2232 o.object_stream = cur_ostream;
... ... @@ -2240,7 +2240,7 @@ QPDFWriter::Members::trimmed_trailer()
2240 2240 {
2241 2241 // Remove keys from the trailer that necessarily have to be replaced when writing the file.
2242 2242  
2243   - Dictionary trailer = pdf.getTrailer().unsafeShallowCopy();
  2243 + Dictionary trailer = qpdf.getTrailer().unsafeShallowCopy();
2244 2244  
2245 2245 // Remove encryption keys
2246 2246 trailer.erase("/ID");
... ... @@ -2265,8 +2265,8 @@ QPDFWriter::Members::trimmed_trailer()
2265 2265 void
2266 2266 QPDFWriter::Members::prepareFileForWrite()
2267 2267 {
2268   - pdf.fixDanglingReferences();
2269   - auto root = pdf.getRoot();
  2268 + qpdf.fixDanglingReferences();
  2269 + auto root = qpdf.getRoot();
2270 2270 auto oh = root.getKey("/Extensions");
2271 2271 if (oh.isDictionary()) {
2272 2272 const bool extensions_indirect = oh.isIndirect();
... ... @@ -2335,7 +2335,7 @@ QPDFWriter::Members::doWriteSetup()
2335 2335 }
2336 2336  
2337 2337 if (preserve_encryption) {
2338   - copyEncryptionParameters(pdf);
  2338 + copyEncryptionParameters(qpdf);
2339 2339 }
2340 2340  
2341 2341 if (!forced_pdf_version.empty()) {
... ... @@ -2380,7 +2380,7 @@ QPDFWriter::Members::doWriteSetup()
2380 2380 if (!obj.streams_empty) {
2381 2381 if (linearized) {
2382 2382 // Page dictionaries are not allowed to be compressed objects.
2383   - for (auto& page: pdf.getAllPages()) {
  2383 + for (auto& page: pages) {
2384 2384 if (obj[page].object_stream > 0) {
2385 2385 obj[page].object_stream = 0;
2386 2386 }
... ... @@ -2416,7 +2416,7 @@ QPDFWriter::Members::doWriteSetup()
2416 2416 }
2417 2417 }
2418 2418  
2419   - setMinimumPDFVersion(pdf.getPDFVersion(), pdf.getExtensionLevel());
  2419 + setMinimumPDFVersion(qpdf.getPDFVersion(), qpdf.getExtensionLevel());
2420 2420 final_pdf_version = min_pdf_version;
2421 2421 final_extension_level = min_extension_level;
2422 2422 if (!forced_pdf_version.empty()) {
... ... @@ -2438,7 +2438,7 @@ QPDFWriter::Members::write()
2438 2438  
2439 2439 // Set up progress reporting. For linearized files, we write two passes. events_expected is an
2440 2440 // approximation, but it's good enough for progress reporting, which is mostly a guess anyway.
2441   - events_expected = QIntC::to_int(pdf.getObjectCount() * (linearized ? 2 : 1));
  2441 + events_expected = QIntC::to_int(qpdf.getObjectCount() * (linearized ? 2 : 1));
2442 2442  
2443 2443 prepareFileForWrite();
2444 2444  
... ... @@ -2910,14 +2910,11 @@ QPDFWriter::Members::writeLinearized()
2910 2910 openObject(lindict_id);
2911 2911 write("<<");
2912 2912 if (pass == 2) {
2913   - std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
2914   - int first_page_object = obj[pages.at(0)].renumber;
2915   -
2916 2913 write(" /Linearized 1 /L ").write(file_size + hint_length);
2917 2914 // Implementation note 121 states that a space is mandatory after this open bracket.
2918 2915 write(" /H [ ").write(new_obj[hint_id].xref.getOffset()).write(" ");
2919 2916 write(hint_length);
2920   - write(" ] /O ").write(first_page_object);
  2917 + write(" ] /O ").write(obj[pages.all().at(0)].renumber);
2921 2918 write(" /E ").write(part6_end_offset + hint_length);
2922 2919 write(" /N ").write(pages.size());
2923 2920 write(" /T ").write(space_before_zero + hint_length);
... ... @@ -3110,7 +3107,7 @@ void
3110 3107 QPDFWriter::Members::enqueueObjectsStandard()
3111 3108 {
3112 3109 if (preserve_unreferenced_objects) {
3113   - for (auto const& oh: pdf.getAllObjects()) {
  3110 + for (auto const& oh: qpdf.getAllObjects()) {
3114 3111 enqueueObject(oh);
3115 3112 }
3116 3113 }
... ... @@ -3136,20 +3133,18 @@ QPDFWriter::Members::enqueueObjectsPCLm()
3136 3133 std::string image_transform_content = "q /image Do Q\n";
3137 3134  
3138 3135 // enqueue all pages first
3139   - std::vector<QPDFObjectHandle> all = pdf.getAllPages();
3140   - for (auto& page: all) {
  3136 + for (auto& page: pages) {
3141 3137 // enqueue page
3142 3138 enqueueObject(page);
3143 3139  
3144 3140 // enqueue page contents stream
3145   - enqueueObject(page.getKey("/Contents"));
  3141 + enqueueObject(page["/Contents"]);
3146 3142  
3147 3143 // enqueue all the strips for each page
3148   - QPDFObjectHandle strips = page.getKey("/Resources").getKey("/XObject");
3149   - for (auto& image: strips.as_dictionary()) {
  3144 + for (auto& image: Dictionary(page["/Resources"]["/XObject"])) {
3150 3145 if (!image.second.null()) {
3151 3146 enqueueObject(image.second);
3152   - enqueueObject(QPDFObjectHandle::newStream(&pdf, image_transform_content));
  3147 + enqueueObject(QPDFObjectHandle::newStream(&qpdf, image_transform_content));
3153 3148 }
3154 3149 }
3155 3150 }
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -30,7 +30,7 @@ using Streams = QPDF::Doc::Objects::Streams;
30 30 bool
31 31 Streams::immediate_copy_from() const
32 32 {
33   - return qpdf_.m->immediate_copy_from;
  33 + return qpdf.m->immediate_copy_from;
34 34 }
35 35  
36 36 class Streams::Copier final: public QPDFObjectHandle::StreamDataProvider
... ... @@ -83,10 +83,10 @@ class Streams::Copier final: public QPDFObjectHandle::StreamDataProvider
83 83 if (data != copied_data.end()) {
84 84 auto& fd = data->second;
85 85 QTC::TC("qpdf", "QPDF pipe foreign encrypted stream", fd.encp->encrypted ? 0 : 1);
86   - if (streams.qpdf().pipeStreamData(
  86 + if (streams.qpdf.pipeStreamData(
87 87 fd.encp,
88 88 fd.file,
89   - streams.qpdf(),
  89 + streams.qpdf,
90 90 fd.source_og,
91 91 fd.offset,
92 92 fd.length,
... ... @@ -128,8 +128,8 @@ class Streams::Copier final: public QPDFObjectHandle::StreamDataProvider
128 128 std::map<QPDFObjGen, Data> copied_data;
129 129 };
130 130  
131   -Streams::Streams(QPDF& qpdf) :
132   - qpdf_(qpdf),
  131 +Streams::Streams(Common& common) :
  132 + Common(common),
133 133 copier_(std::make_shared<Copier>(*this))
134 134 {
135 135 }
... ...
libqpdf/QPDF_encryption.cc
... ... @@ -734,15 +734,16 @@ QPDF::EncryptionParameters::initialize(QPDF&amp; qpdf)
734 734 }
735 735 encryption_initialized = true;
736 736  
  737 + auto& c = qpdf.m->c;
737 738 auto& qm = *qpdf.m;
738 739 auto& trailer = qm.trailer;
739 740 auto& file = qm.file;
740 741  
741   - auto warn_damaged_pdf = [&qpdf](std::string const& msg) {
742   - qpdf.warn(qpdf.damagedPDF("encryption dictionary", msg));
  742 + auto warn_damaged_pdf = [&qpdf, c](std::string const& msg) {
  743 + qpdf.warn(c.damagedPDF("encryption dictionary", msg));
743 744 };
744 745 auto throw_damaged_pdf = [&qpdf](std::string const& msg) {
745   - throw qpdf.damagedPDF("encryption dictionary", msg);
  746 + throw qpdf.m->c.damagedPDF("encryption dictionary", msg);
746 747 };
747 748 auto unsupported = [&file](std::string const& msg) -> QPDFExc {
748 749 return {
... ... @@ -770,14 +771,14 @@ QPDF::EncryptionParameters::initialize(QPDF&amp; qpdf)
770 771 if (id_obj.size() != 2 || !id_obj.getArrayItem(0).isString()) {
771 772 // Treating a missing ID as the empty string enables qpdf to decrypt some invalid encrypted
772 773 // files with no /ID that poppler can read but Adobe Reader can't.
773   - qpdf.warn(qpdf.damagedPDF("trailer", "invalid /ID in trailer dictionary"));
  774 + qpdf.warn(qpdf.m->c.damagedPDF("trailer", "invalid /ID in trailer dictionary"));
774 775 } else {
775 776 id1 = id_obj.getArrayItem(0).getStringValue();
776 777 }
777 778  
778 779 auto encryption_dict = trailer.getKey("/Encrypt");
779 780 if (!encryption_dict.isDictionary()) {
780   - throw qpdf.damagedPDF("/Encrypt in trailer dictionary is not a dictionary");
  781 + throw qpdf.m->c.damagedPDF("/Encrypt in trailer dictionary is not a dictionary");
781 782 }
782 783  
783 784 if (Name(encryption_dict["/Filter"]) != "/Standard") {
... ... @@ -984,7 +985,7 @@ QPDF::decryptString(std::string&amp; str, QPDFObjGen og)
984 985 break;
985 986  
986 987 default:
987   - warn(damagedPDF(
  988 + warn(m->c.damagedPDF(
988 989 "unknown encryption filter for strings (check /StrF in "
989 990 "/Encrypt dictionary); strings may be decrypted improperly"));
990 991 // To avoid repeated warnings, reset cf_string. Assume we'd want to use AES if V == 4.
... ... @@ -1017,7 +1018,8 @@ QPDF::decryptString(std::string&amp; str, QPDFObjGen og)
1017 1018 } catch (QPDFExc&) {
1018 1019 throw;
1019 1020 } catch (std::runtime_error& e) {
1020   - throw damagedPDF("error decrypting string for object " + og.unparse() + ": " + e.what());
  1021 + throw m->c.damagedPDF(
  1022 + "error decrypting string for object " + og.unparse() + ": " + e.what());
1021 1023 }
1022 1024 }
1023 1025  
... ...
libqpdf/QPDF_json.cc
... ... @@ -460,110 +460,111 @@ QPDF::JSONReactor::dictionaryItem(std::string const&amp; key, JSON const&amp; value)
460 460 next_state = st_ignore;
461 461 auto state = stack.back().state;
462 462 if (state == st_ignore) {
463   - QTC::TC("qpdf", "QPDF_json ignoring in st_ignore");
464   - // ignore
465   - } else if (state == st_top) {
  463 + return true; // ignore
  464 + }
  465 + if (state == st_top) {
466 466 if (key == "qpdf") {
467   - this->saw_qpdf = true;
  467 + saw_qpdf = true;
468 468 if (!value.isArray()) {
469   - QTC::TC("qpdf", "QPDF_json qpdf not array");
470 469 error(value.getStart(), "\"qpdf\" must be an array");
471 470 } else {
472 471 next_state = st_qpdf;
473 472 }
474   - } else {
475   - // Ignore all other fields.
476   - QTC::TC("qpdf", "QPDF_json ignoring unknown top-level key");
  473 + return true;
477 474 }
478   - } else if (state == st_qpdf_meta) {
  475 + return true; // Ignore all other fields.
  476 + }
  477 +
  478 + if (state == st_qpdf_meta) {
479 479 if (key == "pdfversion") {
480   - this->saw_pdf_version = true;
  480 + saw_pdf_version = true;
481 481 std::string v;
482   - bool okay = false;
483 482 if (value.getString(v)) {
484 483 std::string version;
485 484 char const* p = v.c_str();
486 485 if (QPDF::validatePDFVersion(p, version) && (*p == '\0')) {
487   - this->pdf.m->pdf_version = version;
488   - okay = true;
  486 + pdf.m->pdf_version = version;
  487 + return true;
489 488 }
490 489 }
491   - if (!okay) {
492   - QTC::TC("qpdf", "QPDF_json bad pdf version");
493   - error(value.getStart(), "invalid PDF version (must be \"x.y\")");
494   - }
495   - } else if (key == "jsonversion") {
496   - this->saw_json_version = true;
  490 + error(value.getStart(), "invalid PDF version (must be \"x.y\")");
  491 + return true;
  492 + }
  493 + if (key == "jsonversion") {
  494 + saw_json_version = true;
497 495 std::string v;
498   - bool okay = false;
499 496 if (value.getNumber(v)) {
500 497 std::string version;
501 498 if (QUtil::string_to_int(v.c_str()) == 2) {
502   - okay = true;
  499 + return true;
503 500 }
504 501 }
505   - if (!okay) {
506   - QTC::TC("qpdf", "QPDF_json bad json version");
507   - error(value.getStart(), "invalid JSON version (must be numeric value 2)");
508   - }
509   - } else if (key == "pushedinheritedpageresources") {
  502 + error(value.getStart(), "invalid JSON version (must be numeric value 2)");
  503 + return true;
  504 + }
  505 + if (key == "pushedinheritedpageresources") {
510 506 bool v;
511 507 if (value.getBool(v)) {
512   - if (!this->must_be_complete && v) {
513   - this->pdf.pushInheritedAttributesToPage();
  508 + if (!must_be_complete && v) {
  509 + pdf.pushInheritedAttributesToPage();
514 510 }
515   - } else {
516   - QTC::TC("qpdf", "QPDF_json bad pushedinheritedpageresources");
517   - error(value.getStart(), "pushedinheritedpageresources must be a boolean");
  511 + return true;
518 512 }
519   - } else if (key == "calledgetallpages") {
  513 + error(value.getStart(), "pushedinheritedpageresources must be a boolean");
  514 + return true;
  515 + }
  516 + if (key == "calledgetallpages") {
520 517 bool v;
521 518 if (value.getBool(v)) {
522   - if (!this->must_be_complete && v) {
523   - this->pdf.getAllPages();
  519 + if (!must_be_complete && v) {
  520 + (void)pdf.doc().pages().all();
524 521 }
525   - } else {
526   - QTC::TC("qpdf", "QPDF_json bad calledgetallpages");
527   - error(value.getStart(), "calledgetallpages must be a boolean");
  522 + return true;
528 523 }
529   - } else {
530   - // ignore unknown keys for forward compatibility and to skip keys we don't care about
531   - // like "maxobjectid".
532   - QTC::TC("qpdf", "QPDF_json ignore second-level key");
  524 + error(value.getStart(), "calledgetallpages must be a boolean");
  525 + return true;
533 526 }
534   - } else if (state == st_objects) {
535   - int obj = 0;
536   - int gen = 0;
  527 + // ignore unknown keys for forward compatibility and to skip keys we don't care about
  528 + // like "maxobjectid".
  529 + return true;
  530 + }
  531 +
  532 + if (state == st_objects) {
537 533 if (key == "trailer") {
538   - this->saw_trailer = true;
539   - this->cur_object = "trailer";
  534 + saw_trailer = true;
  535 + cur_object = "trailer";
540 536 setNextStateIfDictionary(key, value, st_trailer);
541   - } else if (is_obj_key(key, obj, gen)) {
542   - this->cur_object = key;
  537 + return true;
  538 + }
  539 +
  540 + int obj = 0;
  541 + int gen = 0;
  542 + if (is_obj_key(key, obj, gen)) {
  543 + cur_object = key;
543 544 if (setNextStateIfDictionary(key, value, st_object_top)) {
544 545 next_obj = objects.getObjectForJSON(obj, gen);
545 546 }
546   - } else {
547   - QTC::TC("qpdf", "QPDF_json bad object key");
548   - error(value.getStart(), "object key should be \"trailer\" or \"obj:n n R\"");
549   - }
550   - } else if (state == st_object_top) {
551   - if (stack.empty()) {
552   - throw std::logic_error("stack empty in st_object_top");
  547 + return true;
553 548 }
  549 + error(value.getStart(), "object key should be \"trailer\" or \"obj:n n R\"");
  550 + return true;
  551 + }
  552 +
  553 + if (state == st_object_top) {
  554 + util::assertion(!stack.empty(), "QPDF_json: stack empty in st_object_top");
554 555 auto& tos = stack.back();
555   - if (!tos.object) {
556   - throw std::logic_error("current object uninitialized in st_object_top");
557   - }
  556 + util::assertion(!!tos.object, "current object uninitialized in st_object_top");
558 557 if (key == "value") {
559 558 // Don't use setNextStateIfDictionary since this can have any type.
560   - this->saw_value = true;
  559 + saw_value = true;
561 560 replaceObject(makeObject(value), value);
562 561 next_state = st_object;
563   - } else if (key == "stream") {
564   - this->saw_stream = true;
  562 + return true;
  563 + }
  564 + if (key == "stream") {
  565 + saw_stream = true;
565 566 if (setNextStateIfDictionary(key, value, st_stream)) {
566   - this->this_stream_needs_data = false;
  567 + this_stream_needs_data = false;
567 568 if (tos.object.isStream()) {
568 569 QTC::TC("qpdf", "QPDF_json updating existing stream");
569 570 } else {
... ... @@ -574,97 +575,87 @@ QPDF::JSONReactor::dictionaryItem(std::string const&amp; key, JSON const&amp; value)
574 575 value);
575 576 }
576 577 next_obj = tos.object;
577   - } else {
578   - // Error message already given above
579   - QTC::TC("qpdf", "QPDF_json stream not a dictionary");
  578 + return true;
580 579 }
581   - } else {
582   - // Ignore unknown keys for forward compatibility
583   - QTC::TC("qpdf", "QPDF_json ignore unknown key in object_top");
  580 + return true; // Error message already given above
584 581 }
585   - } else if (state == st_trailer) {
  582 + return true; // Ignore unknown keys for forward compatibility
  583 + }
  584 +
  585 + if (state == st_trailer) {
586 586 if (key == "value") {
587   - this->saw_value = true;
  587 + saw_value = true;
588 588 // The trailer must be a dictionary, so we can use setNextStateIfDictionary.
589 589 if (setNextStateIfDictionary("trailer.value", value, st_object)) {
590   - this->pdf.m->trailer = makeObject(value);
591   - setObjectDescription(this->pdf.m->trailer, value);
  590 + pdf.m->trailer = makeObject(value);
  591 + setObjectDescription(pdf.m->trailer, value);
592 592 }
593   - } else if (key == "stream") {
  593 + return true;
  594 + }
  595 + if (key == "stream") {
594 596 // Don't need to set saw_stream here since there's already an error.
595   - QTC::TC("qpdf", "QPDF_json trailer stream");
596 597 error(value.getStart(), "the trailer may not be a stream");
597   - } else {
598   - // Ignore unknown keys for forward compatibility
599   - QTC::TC("qpdf", "QPDF_json ignore unknown key in trailer");
600   - }
601   - } else if (state == st_stream) {
602   - if (stack.empty()) {
603   - throw std::logic_error("stack empty in st_stream");
  598 + return true;
604 599 }
  600 + return true; // Ignore unknown keys for forward compatibility
  601 + }
  602 +
  603 + if (state == st_stream) {
  604 + util::assertion(!stack.empty(), "stack empty in st_stream");
605 605 auto& tos = stack.back();
606   - if (!tos.object.isStream()) {
607   - throw std::logic_error("current object is not stream in st_stream");
608   - }
  606 + util::assertion(tos.object.isStream(), "current object is not stream in st_stream");
609 607 if (key == "dict") {
610   - this->saw_dict = true;
  608 + saw_dict = true;
611 609 if (setNextStateIfDictionary("stream.dict", value, st_object)) {
612 610 tos.object.replaceDict(makeObject(value));
613   - } else {
614   - // An error had already been given by setNextStateIfDictionary
615   - QTC::TC("qpdf", "QPDF_json stream dict not dict");
  611 + return true;
616 612 }
617   - } else if (key == "data") {
618   - this->saw_data = true;
  613 + return true; // An error had already been given by setNextStateIfDictionary
  614 + }
  615 + if (key == "data") {
  616 + saw_data = true;
619 617 std::string v;
620 618 if (!value.getString(v)) {
621   - QTC::TC("qpdf", "QPDF_json stream data not string");
622 619 error(value.getStart(), "\"stream.data\" must be a string");
623 620 tos.object.replaceStreamData("", {}, {});
624   - } else {
625   - // The range includes the quotes.
626   - auto start = value.getStart() + 1;
627   - auto end = value.getEnd() - 1;
628   - if (end < start) {
629   - throw std::logic_error("QPDF_json: JSON string length < 0");
630   - }
631   - tos.object.replaceStreamData(provide_data(is, start, end), {}, {});
  621 + return true;
632 622 }
633   - } else if (key == "datafile") {
634   - this->saw_datafile = true;
  623 + // The range includes the quotes.
  624 + auto start = value.getStart() + 1;
  625 + auto end = value.getEnd() - 1;
  626 + util::assertion(end >= start, "QPDF_json: JSON string length < 0");
  627 + tos.object.replaceStreamData(provide_data(is, start, end), {}, {});
  628 + return true;
  629 + }
  630 + if (key == "datafile") {
  631 + saw_datafile = true;
635 632 std::string filename;
636 633 if (!value.getString(filename)) {
637   - QTC::TC("qpdf", "QPDF_json stream datafile not string");
638 634 error(
639 635 value.getStart(),
640 636 "\"stream.datafile\" must be a string containing a file name");
641 637 tos.object.replaceStreamData("", {}, {});
642   - } else {
643   - tos.object.replaceStreamData(QUtil::file_provider(filename), {}, {});
  638 + return true;
644 639 }
645   - } else {
646   - // Ignore unknown keys for forward compatibility.
647   - QTC::TC("qpdf", "QPDF_json ignore unknown key in stream");
648   - }
649   - } else if (state == st_object) {
650   - if (stack.empty()) {
651   - throw std::logic_error("stack empty in st_object");
  640 + tos.object.replaceStreamData(QUtil::file_provider(filename), {}, {});
  641 + return true;
652 642 }
653   - auto& tos = stack.back();
654   - auto dict = tos.object;
655   - if (dict.isStream()) {
656   - dict = dict.getDict();
657   - }
658   - if (!dict.isDictionary()) {
659   - throw std::logic_error(
660   - "current object is not stream or dictionary in st_object dictionary item");
661   - }
662   - dict.replaceKey(
663   - is_pdf_name(key) ? QPDFObjectHandle::parse(key.substr(2)).getName() : key,
664   - makeObject(value));
665   - } else {
666   - throw std::logic_error("QPDF_json: unknown state " + std::to_string(state));
  643 + return true; // Ignore unknown keys for forward compatibility.
667 644 }
  645 +
  646 + util::assertion(state == st_object, "QPDF_json: unknown state " + std::to_string(state));
  647 + util::assertion(!stack.empty(), "stack empty in st_object");
  648 + auto& tos = stack.back();
  649 + auto dict = tos.object;
  650 + if (dict.isStream()) {
  651 + dict = dict.getDict();
  652 + }
  653 + util::assertion(
  654 + dict.isDictionary(),
  655 + "current object is not stream or dictionary in st_object dictionary item");
  656 + dict.replaceKey(
  657 + is_pdf_name(key) ? QPDFObjectHandle::parse(key.substr(2)).getName() : key,
  658 + makeObject(value));
668 659 return true;
669 660 }
670 661  
... ...
libqpdf/QPDF_linearization.cc
... ... @@ -69,11 +69,298 @@ load_vector_vector(
69 69 bit_stream.skipToNextByte();
70 70 }
71 71  
  72 +QPDF::ObjUser::ObjUser(user_e type) :
  73 + ou_type(type)
  74 +{
  75 + qpdf_expect(type == ou_root);
  76 +}
  77 +
  78 +QPDF::ObjUser::ObjUser(user_e type, size_t pageno) :
  79 + ou_type(type),
  80 + pageno(pageno)
  81 +{
  82 + qpdf_expect(type == ou_page || type == ou_thumb);
  83 +}
  84 +
  85 +QPDF::ObjUser::ObjUser(user_e type, std::string const& key) :
  86 + ou_type(type),
  87 + key(key)
  88 +{
  89 + qpdf_expect(type == ou_trailer_key || type == ou_root_key);
  90 +}
  91 +
  92 +bool
  93 +QPDF::ObjUser::operator<(ObjUser const& rhs) const
  94 +{
  95 + if (ou_type < rhs.ou_type) {
  96 + return true;
  97 + }
  98 + if (ou_type == rhs.ou_type) {
  99 + if (pageno < rhs.pageno) {
  100 + return true;
  101 + }
  102 + if (pageno == rhs.pageno) {
  103 + return key < rhs.key;
  104 + }
  105 + }
  106 + return false;
  107 +}
  108 +
  109 +QPDF::UpdateObjectMapsFrame::UpdateObjectMapsFrame(
  110 + QPDF::ObjUser const& ou, QPDFObjectHandle oh, bool top) :
  111 + ou(ou),
  112 + oh(oh),
  113 + top(top)
  114 +{
  115 +}
  116 +
  117 +void
  118 +QPDF::optimize(
  119 + std::map<int, int> const& object_stream_data,
  120 + bool allow_changes,
  121 + std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
  122 +{
  123 + m->lin.optimize_internal(object_stream_data, allow_changes, skip_stream_parameters);
  124 +}
  125 +
  126 +void
  127 +Lin::optimize(
  128 + QPDFWriter::ObjTable const& obj, std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
  129 +{
  130 + optimize_internal(obj, true, skip_stream_parameters);
  131 +}
  132 +
  133 +template <typename T>
  134 +void
  135 +Lin::optimize_internal(
  136 + T const& object_stream_data,
  137 + bool allow_changes,
  138 + std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
  139 +{
  140 + if (!m->obj_user_to_objects.empty()) {
  141 + // already optimized
  142 + return;
  143 + }
  144 +
  145 + // The PDF specification indicates that /Outlines is supposed to be an indirect reference. Force
  146 + // it to be so if it exists and is direct. (This has been seen in the wild.)
  147 + QPDFObjectHandle root = qpdf.getRoot();
  148 + if (root.getKey("/Outlines").isDictionary()) {
  149 + QPDFObjectHandle outlines = root.getKey("/Outlines");
  150 + if (!outlines.isIndirect()) {
  151 + root.replaceKey("/Outlines", qpdf.makeIndirectObject(outlines));
  152 + }
  153 + }
  154 +
  155 + // Traverse pages tree pushing all inherited resources down to the page level. This also
  156 + // initializes m->all_pages.
  157 + m->pages.pushInheritedAttributesToPage(allow_changes, false);
  158 + // Traverse pages
  159 +
  160 + size_t n = 0;
  161 + for (auto const& page: m->pages) {
  162 + updateObjectMaps(ObjUser(ObjUser::ou_page, n), page, skip_stream_parameters);
  163 + ++n;
  164 + }
  165 +
  166 + // Traverse document-level items
  167 + for (auto const& [key, value]: m->trailer.as_dictionary()) {
  168 + if (key == "/Root") {
  169 + // handled separately
  170 + } else {
  171 + if (!value.null()) {
  172 + updateObjectMaps(
  173 + ObjUser(ObjUser::ou_trailer_key, key), value, skip_stream_parameters);
  174 + }
  175 + }
  176 + }
  177 +
  178 + for (auto const& [key, value]: root.as_dictionary()) {
  179 + // Technically, /I keys from /Thread dictionaries are supposed to be handled separately, but
  180 + // we are going to disregard that specification for now. There is loads of evidence that
  181 + // pdlin and Acrobat both disregard things like this from time to time, so this is almost
  182 + // certain not to cause any problems.
  183 + if (!value.null()) {
  184 + updateObjectMaps(ObjUser(ObjUser::ou_root_key, key), value, skip_stream_parameters);
  185 + }
  186 + }
  187 +
  188 + ObjUser root_ou = ObjUser(ObjUser::ou_root);
  189 + auto root_og = QPDFObjGen(root.getObjGen());
  190 + m->obj_user_to_objects[root_ou].insert(root_og);
  191 + m->object_to_obj_users[root_og].insert(root_ou);
  192 +
  193 + filterCompressedObjects(object_stream_data);
  194 +}
  195 +
  196 +void
  197 +Lin::updateObjectMaps(
  198 + ObjUser const& first_ou,
  199 + QPDFObjectHandle first_oh,
  200 + std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
  201 +{
  202 + QPDFObjGen::set visited;
  203 + std::vector<UpdateObjectMapsFrame> pending;
  204 + pending.emplace_back(first_ou, first_oh, true);
  205 + // Traverse the object tree from this point taking care to avoid crossing page boundaries.
  206 + std::unique_ptr<ObjUser> thumb_ou;
  207 + while (!pending.empty()) {
  208 + auto cur = pending.back();
  209 + pending.pop_back();
  210 +
  211 + bool is_page_node = false;
  212 +
  213 + if (cur.oh.isDictionaryOfType("/Page")) {
  214 + is_page_node = true;
  215 + if (!cur.top) {
  216 + continue;
  217 + }
  218 + }
  219 +
  220 + if (cur.oh.isIndirect()) {
  221 + QPDFObjGen og(cur.oh.getObjGen());
  222 + if (!visited.add(og)) {
  223 + QTC::TC("qpdf", "QPDF opt loop detected");
  224 + continue;
  225 + }
  226 + m->obj_user_to_objects[cur.ou].insert(og);
  227 + m->object_to_obj_users[og].insert(cur.ou);
  228 + }
  229 +
  230 + if (cur.oh.isArray()) {
  231 + for (auto const& item: cur.oh.as_array()) {
  232 + pending.emplace_back(cur.ou, item, false);
  233 + }
  234 + } else if (cur.oh.isDictionary() || cur.oh.isStream()) {
  235 + QPDFObjectHandle dict = cur.oh;
  236 + bool is_stream = cur.oh.isStream();
  237 + int ssp = 0;
  238 + if (is_stream) {
  239 + dict = cur.oh.getDict();
  240 + if (skip_stream_parameters) {
  241 + ssp = skip_stream_parameters(cur.oh);
  242 + }
  243 + }
  244 +
  245 + for (auto& [key, value]: dict.as_dictionary()) {
  246 + if (value.null()) {
  247 + continue;
  248 + }
  249 +
  250 + if (is_page_node && (key == "/Thumb")) {
  251 + // Traverse page thumbnail dictionaries as a special case. There can only ever
  252 + // be one /Thumb key on a page, and we see at most one page node per call.
  253 + thumb_ou = std::make_unique<ObjUser>(ObjUser::ou_thumb, cur.ou.pageno);
  254 + pending.emplace_back(*thumb_ou, dict.getKey(key), false);
  255 + } else if (is_page_node && (key == "/Parent")) {
  256 + // Don't traverse back up the page tree
  257 + } else if (
  258 + ((ssp >= 1) && (key == "/Length")) ||
  259 + ((ssp >= 2) && ((key == "/Filter") || (key == "/DecodeParms")))) {
  260 + // Don't traverse into stream parameters that we are not going to write.
  261 + } else {
  262 + pending.emplace_back(cur.ou, value, false);
  263 + }
  264 + }
  265 + }
  266 + }
  267 +}
  268 +
  269 +void
  270 +Lin::filterCompressedObjects(std::map<int, int> const& object_stream_data)
  271 +{
  272 + if (object_stream_data.empty()) {
  273 + return;
  274 + }
  275 +
  276 + // Transform object_to_obj_users and obj_user_to_objects so that they refer only to uncompressed
  277 + // objects. If something is a user of a compressed object, then it is really a user of the
  278 + // object stream that contains it.
  279 +
  280 + std::map<ObjUser, std::set<QPDFObjGen>> t_obj_user_to_objects;
  281 + std::map<QPDFObjGen, std::set<ObjUser>> t_object_to_obj_users;
  282 +
  283 + for (auto const& i1: m->obj_user_to_objects) {
  284 + ObjUser const& ou = i1.first;
  285 + // Loop over objects.
  286 + for (auto const& og: i1.second) {
  287 + auto i2 = object_stream_data.find(og.getObj());
  288 + if (i2 == object_stream_data.end()) {
  289 + t_obj_user_to_objects[ou].insert(og);
  290 + } else {
  291 + t_obj_user_to_objects[ou].insert(QPDFObjGen(i2->second, 0));
  292 + }
  293 + }
  294 + }
  295 +
  296 + for (auto const& i1: m->object_to_obj_users) {
  297 + QPDFObjGen const& og = i1.first;
  298 + // Loop over obj_users.
  299 + for (auto const& ou: i1.second) {
  300 + auto i2 = object_stream_data.find(og.getObj());
  301 + if (i2 == object_stream_data.end()) {
  302 + t_object_to_obj_users[og].insert(ou);
  303 + } else {
  304 + t_object_to_obj_users[QPDFObjGen(i2->second, 0)].insert(ou);
  305 + }
  306 + }
  307 + }
  308 +
  309 + m->obj_user_to_objects = t_obj_user_to_objects;
  310 + m->object_to_obj_users = t_object_to_obj_users;
  311 +}
  312 +
  313 +void
  314 +Lin::filterCompressedObjects(QPDFWriter::ObjTable const& obj)
  315 +{
  316 + if (obj.getStreamsEmpty()) {
  317 + return;
  318 + }
  319 +
  320 + // Transform object_to_obj_users and obj_user_to_objects so that they refer only to uncompressed
  321 + // objects. If something is a user of a compressed object, then it is really a user of the
  322 + // object stream that contains it.
  323 +
  324 + std::map<ObjUser, std::set<QPDFObjGen>> t_obj_user_to_objects;
  325 + std::map<QPDFObjGen, std::set<ObjUser>> t_object_to_obj_users;
  326 +
  327 + for (auto const& i1: m->obj_user_to_objects) {
  328 + ObjUser const& ou = i1.first;
  329 + // Loop over objects.
  330 + for (auto const& og: i1.second) {
  331 + if (obj.contains(og)) {
  332 + if (auto const& i2 = obj[og].object_stream; i2 <= 0) {
  333 + t_obj_user_to_objects[ou].insert(og);
  334 + } else {
  335 + t_obj_user_to_objects[ou].insert(QPDFObjGen(i2, 0));
  336 + }
  337 + }
  338 + }
  339 + }
  340 +
  341 + for (auto const& i1: m->object_to_obj_users) {
  342 + QPDFObjGen const& og = i1.first;
  343 + if (obj.contains(og)) {
  344 + // Loop over obj_users.
  345 + for (auto const& ou: i1.second) {
  346 + if (auto i2 = obj[og].object_stream; i2 <= 0) {
  347 + t_object_to_obj_users[og].insert(ou);
  348 + } else {
  349 + t_object_to_obj_users[QPDFObjGen(i2, 0)].insert(ou);
  350 + }
  351 + }
  352 + }
  353 + }
  354 +
  355 + m->obj_user_to_objects = t_obj_user_to_objects;
  356 + m->object_to_obj_users = t_object_to_obj_users;
  357 +}
  358 +
72 359 void
73 360 Lin::linearizationWarning(std::string_view msg)
74 361 {
75 362 m->linearization_warnings = true;
76   - qpdf.warn(qpdf_e_linearization, "", 0, std::string(msg));
  363 + warn(qpdf_e_linearization, "", 0, std::string(msg));
77 364 }
78 365  
79 366 bool
... ... @@ -166,19 +453,19 @@ Lin::readLinearizationData()
166 453 Integer P = P_oh; // first page number
167 454 QTC::TC("qpdf", "QPDF P absent in lindict", P ? 0 : 1);
168 455  
169   - qpdf.no_ci_stop_if(
  456 + no_ci_stop_if(
170 457 !(H && O && E && N && T && (P || P_oh.null())),
171 458 "some keys in linearization dictionary are of the wrong type",
172 459 "linearization dictionary" //
173 460 );
174 461  
175   - qpdf.no_ci_stop_if(
  462 + no_ci_stop_if(
176 463 !(H_size == 2 || H_size == 4),
177 464 "H has the wrong number of items",
178 465 "linearization dictionary" //
179 466 );
180 467  
181   - qpdf.no_ci_stop_if(
  468 + no_ci_stop_if(
182 469 !(H_0 && H_1 && (H_size == 2 || (H_2 && H_3))),
183 470 "some H items are of the wrong type",
184 471 "linearization dictionary" //
... ... @@ -188,8 +475,8 @@ Lin::readLinearizationData()
188 475  
189 476 // Various places in the code use linp.npages, which is initialized from N, to pre-allocate
190 477 // memory, so make sure it's accurate and bail right now if it's not.
191   - qpdf.no_ci_stop_if(
192   - N != qpdf.getAllPages().size(),
  478 + no_ci_stop_if(
  479 + N != pages.size(),
193 480 "/N does not match number of pages",
194 481 "linearization dictionary" //
195 482 );
... ... @@ -234,13 +521,12 @@ Lin::readLinearizationData()
234 521  
235 522 size_t HSi = HS;
236 523 if (HSi < 0 || HSi >= h_size) {
237   - throw qpdf.damagedPDF(
238   - "linearization hint table", "/S (shared object) offset is out of bounds");
  524 + throw damagedPDF("linearization hint table", "/S (shared object) offset is out of bounds");
239 525 }
240 526 readHSharedObject(BitStream(h_buf + HSi, h_size - HSi));
241 527  
242 528 if (HO) {
243   - qpdf.no_ci_stop_if(
  529 + no_ci_stop_if(
244 530 HO < 0 || HO >= h_size,
245 531 "/O (outline) offset is out of bounds",
246 532 "linearization dictionary" //
... ... @@ -257,7 +543,7 @@ Lin::readHintStream(Pipeline&amp; pl, qpdf_offset_t offset, size_t length)
257 543 ObjCache& oc = m->obj_cache[H];
258 544 qpdf_offset_t min_end_offset = oc.end_before_space;
259 545 qpdf_offset_t max_end_offset = oc.end_after_space;
260   - qpdf.no_ci_stop_if(
  546 + no_ci_stop_if(
261 547 !H.isStream(), "hint table is not a stream", "linearization dictionary" //
262 548 );
263 549  
... ... @@ -275,7 +561,7 @@ Lin::readHintStream(Pipeline&amp; pl, qpdf_offset_t offset, size_t length)
275 561 QTC::TC("qpdf", "QPDF hint table length direct");
276 562 }
277 563 qpdf_offset_t computed_end = offset + toO(length);
278   - qpdf.no_ci_stop_if(
  564 + no_ci_stop_if(
279 565 computed_end < min_end_offset || computed_end > max_end_offset,
280 566 "hint table length mismatch (expected = " + std::to_string(computed_end) + "; actual = " +
281 567 std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset) + ")",
... ... @@ -391,20 +677,20 @@ Lin::checkLinearizationInternal()
391 677 // L: file size in bytes -- checked by isLinearized
392 678  
393 679 // O: object number of first page
394   - std::vector<QPDFObjectHandle> const& pages = qpdf.getAllPages();
395   - if (p.first_page_object != pages.at(0).getObjectID()) {
  680 + auto const& all_pages = pages.all();
  681 + if (p.first_page_object != all_pages.at(0).getObjectID()) {
396 682 linearizationWarning("first page object (/O) mismatch");
397 683 }
398 684  
399 685 // N: number of pages
400   - size_t npages = pages.size();
  686 + size_t npages = all_pages.size();
401 687 if (std::cmp_not_equal(p.npages, npages)) {
402 688 // Not tested in the test suite
403 689 linearizationWarning("page count (/N) mismatch");
404 690 }
405 691  
406 692 int i = 0;
407   - for (auto const& page: pages) {
  693 + for (auto const& page: all_pages) {
408 694 if (m->xref_table[page].getType() == 2) {
409 695 linearizationWarning(
410 696 "page dictionary for page " + std::to_string(i) + " is compressed");
... ... @@ -464,7 +750,7 @@ Lin::checkLinearizationInternal()
464 750 // are present. In that case, it would probably agree with pdlin. As of this writing, the test
465 751 // suite doesn't contain any files with threads.
466 752  
467   - qpdf.no_ci_stop_if(
  753 + no_ci_stop_if(
468 754 m->part6.empty(), "linearization part 6 unexpectedly empty" //
469 755 );
470 756 qpdf_offset_t min_E = -1;
... ... @@ -486,22 +772,22 @@ Lin::checkLinearizationInternal()
486 772 // Check hint tables
487 773  
488 774 std::map<int, int> shared_idx_to_obj;
489   - checkHSharedObject(pages, shared_idx_to_obj);
490   - checkHPageOffset(pages, shared_idx_to_obj);
  775 + checkHSharedObject(all_pages, shared_idx_to_obj);
  776 + checkHPageOffset(all_pages, shared_idx_to_obj);
491 777 checkHOutlines();
492 778 }
493 779  
494 780 qpdf_offset_t
495 781 Lin::maxEnd(ObjUser const& ou)
496 782 {
497   - qpdf.no_ci_stop_if(
  783 + no_ci_stop_if(
498 784 !m->obj_user_to_objects.contains(ou),
499 785 "no entry in object user table for requested object user" //
500 786 );
501 787  
502 788 qpdf_offset_t end = 0;
503 789 for (auto const& og: m->obj_user_to_objects[ou]) {
504   - qpdf.no_ci_stop_if(
  790 + no_ci_stop_if(
505 791 !m->obj_cache.contains(og), "unknown object referenced in object user table" //
506 792 );
507 793 end = std::max(end, m->obj_cache[og].end_after_space);
... ... @@ -517,7 +803,7 @@ Lin::getLinearizationOffset(QPDFObjGen og)
517 803 if (typ == 1) {
518 804 return entry.getOffset();
519 805 }
520   - qpdf.no_ci_stop_if(
  806 + no_ci_stop_if(
521 807 typ != 2, "getLinearizationOffset called for xref entry not of type 1 or 2" //
522 808 );
523 809 // For compressed objects, return the offset of the object stream that contains them.
... ... @@ -551,7 +837,7 @@ Lin::lengthNextN(int first_object, int n)
551 837 for (int i = 0; i < n; ++i) {
552 838 QPDFObjGen og(first_object + i, 0);
553 839 if (m->xref_table.contains(og)) {
554   - qpdf.no_ci_stop_if(
  840 + no_ci_stop_if(
555 841 !m->obj_cache.contains(og),
556 842 "found unknown object while calculating length for linearization data" //
557 843 );
... ... @@ -585,7 +871,7 @@ Lin::checkHPageOffset(
585 871 qpdf_offset_t table_offset = adjusted_offset(m->page_offset_hints.first_page_offset);
586 872 QPDFObjGen first_page_og(pages.at(0).getObjGen());
587 873 if (!m->xref_table.contains(first_page_og)) {
588   - qpdf.stopOnError("supposed first page object is not known");
  874 + stopOnError("supposed first page object is not known");
589 875 }
590 876 qpdf_offset_t offset = getLinearizationOffset(first_page_og);
591 877 if (table_offset != offset) {
... ... @@ -596,7 +882,7 @@ Lin::checkHPageOffset(
596 882 QPDFObjGen page_og(pages.at(pageno).getObjGen());
597 883 int first_object = page_og.getObj();
598 884 if (!m->xref_table.contains(page_og)) {
599   - qpdf.stopOnError("unknown object in page offset hint table");
  885 + stopOnError("unknown object in page offset hint table");
600 886 }
601 887 offset = getLinearizationOffset(page_og);
602 888  
... ... @@ -636,7 +922,7 @@ Lin::checkHPageOffset(
636 922  
637 923 for (size_t i = 0; i < toS(he.nshared_objects); ++i) {
638 924 int idx = he.shared_identifiers.at(i);
639   - qpdf.no_ci_stop_if(
  925 + no_ci_stop_if(
640 926 !shared_idx_to_obj.contains(idx),
641 927 "unable to get object for item in shared objects hint table");
642 928  
... ... @@ -645,7 +931,7 @@ Lin::checkHPageOffset(
645 931  
646 932 for (size_t i = 0; i < toS(ce.nshared_objects); ++i) {
647 933 int idx = ce.shared_identifiers.at(i);
648   - qpdf.no_ci_stop_if(
  934 + no_ci_stop_if(
649 935 idx >= m->c_shared_object_data.nshared_total,
650 936 "index out of bounds for shared object hint table" //
651 937 );
... ... @@ -718,7 +1004,7 @@ Lin::checkHSharedObject(std::vector&lt;QPDFObjectHandle&gt; const&amp; pages, std::map&lt;int
718 1004  
719 1005 QPDFObjGen og(cur_object, 0);
720 1006 if (!m->xref_table.contains(og)) {
721   - qpdf.stopOnError("unknown object in shared object hint table");
  1007 + stopOnError("unknown object in shared object hint table");
722 1008 }
723 1009 qpdf_offset_t offset = getLinearizationOffset(og);
724 1010 qpdf_offset_t h_offset = adjusted_offset(so.first_shared_offset);
... ... @@ -768,7 +1054,7 @@ Lin::checkHOutlines()
768 1054 return;
769 1055 }
770 1056 QPDFObjGen og(outlines.getObjGen());
771   - qpdf.no_ci_stop_if(
  1057 + no_ci_stop_if(
772 1058 !m->xref_table.contains(og), "unknown object in outlines hint table" //
773 1059 );
774 1060 qpdf_offset_t offset = getLinearizationOffset(og);
... ... @@ -1105,12 +1391,12 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1105 1391  
1106 1392 // We seem to traverse the page tree a lot in this code, but we can address this for a future
1107 1393 // code optimization if necessary. Premature optimization is the root of all evil.
1108   - std::vector<QPDFObjectHandle> pages;
  1394 + std::vector<QPDFObjectHandle> uc_pages;
1109 1395 { // local scope
1110 1396 // Map all page objects to the containing object stream. This should be a no-op in a
1111 1397 // properly linearized file.
1112   - for (auto oh: qpdf.getAllPages()) {
1113   - pages.emplace_back(getUncompressedObject(oh, object_stream_data));
  1398 + for (auto oh: pages) {
  1399 + uc_pages.emplace_back(getUncompressedObject(oh, object_stream_data));
1114 1400 }
1115 1401 }
1116 1402 size_t npages = pages.size();
... ... @@ -1128,7 +1414,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1128 1414  
1129 1415 // Part 4: open document objects. We don't care about the order.
1130 1416  
1131   - qpdf.no_ci_stop_if(
  1417 + no_ci_stop_if(
1132 1418 lc_root.size() != 1, "found other than one root while calculating linearization data" //
1133 1419 );
1134 1420  
... ... @@ -1142,15 +1428,15 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1142 1428 // any option to set this and also disregards /OpenAction. We will do the same.
1143 1429  
1144 1430 // First, place the actual first page object itself.
1145   - qpdf.no_ci_stop_if(
  1431 + no_ci_stop_if(
1146 1432 pages.empty(), "no pages found while calculating linearization data" //
1147 1433 );
1148   - QPDFObjGen first_page_og(pages.at(0).getObjGen());
1149   - qpdf.no_ci_stop_if(
  1434 + QPDFObjGen first_page_og(uc_pages.at(0).getObjGen());
  1435 + no_ci_stop_if(
1150 1436 !lc_first_page_private.erase(first_page_og), "unable to linearize first page" //
1151 1437 );
1152   - m->c_linp.first_page_object = pages.at(0).getObjectID();
1153   - m->part6.emplace_back(pages.at(0));
  1438 + m->c_linp.first_page_object = uc_pages.at(0).getObjectID();
  1439 + m->part6.emplace_back(uc_pages.at(0));
1154 1440  
1155 1441 // The PDF spec "recommends" an order for the rest of the objects, but we are going to disregard
1156 1442 // it except to the extent that it groups private and shared objects contiguously for the sake
... ... @@ -1181,13 +1467,13 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1181 1467 for (size_t i = 1; i < npages; ++i) {
1182 1468 // Place this page's page object
1183 1469  
1184   - QPDFObjGen page_og(pages.at(i).getObjGen());
1185   - qpdf.no_ci_stop_if(
  1470 + QPDFObjGen page_og(uc_pages.at(i).getObjGen());
  1471 + no_ci_stop_if(
1186 1472 !lc_other_page_private.erase(page_og),
1187 1473 "unable to linearize page " + std::to_string(i) //
1188 1474 );
1189 1475  
1190   - m->part7.emplace_back(pages.at(i));
  1476 + m->part7.emplace_back(uc_pages.at(i));
1191 1477  
1192 1478 // Place all non-shared objects referenced by this page, updating the page object count for
1193 1479 // the hint table.
... ... @@ -1195,7 +1481,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1195 1481 m->c_page_offset_data.entries.at(i).nobjects = 1;
1196 1482  
1197 1483 ObjUser ou(ObjUser::ou_page, i);
1198   - qpdf.no_ci_stop_if(
  1484 + no_ci_stop_if(
1199 1485 !m->obj_user_to_objects.contains(ou),
1200 1486 "found unreferenced page while calculating linearization data" //
1201 1487 );
... ... @@ -1231,7 +1517,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1231 1517 // Place the pages tree.
1232 1518 std::set<QPDFObjGen> pages_ogs =
1233 1519 m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")];
1234   - qpdf.no_ci_stop_if(
  1520 + no_ci_stop_if(
1235 1521 pages_ogs.empty(), "found empty pages tree while calculating linearization data" //
1236 1522 );
1237 1523 for (auto const& og: pages_ogs) {
... ... @@ -1243,7 +1529,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1243 1529 // Place private thumbnail images in page order. Slightly more information would be required if
1244 1530 // we were going to bother with thumbnail hint tables.
1245 1531 for (size_t i = 0; i < npages; ++i) {
1246   - QPDFObjectHandle thumb = pages.at(i).getKey("/Thumb");
  1532 + QPDFObjectHandle thumb = uc_pages.at(i).getKey("/Thumb");
1247 1533 thumb = getUncompressedObject(thumb, object_stream_data);
1248 1534 QPDFObjGen thumb_og(thumb.getObjGen());
1249 1535 // Output the thumbnail itself
... ... @@ -1288,7 +1574,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1288 1574 size_t num_placed =
1289 1575 m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size();
1290 1576 size_t num_wanted = m->object_to_obj_users.size();
1291   - qpdf.no_ci_stop_if(
  1577 + no_ci_stop_if(
1292 1578 // This can happen with damaged files, e.g. if the root is part of the the pages tree.
1293 1579 num_placed != num_wanted,
1294 1580 "QPDF::calculateLinearizationData: wrong number of objects placed (num_placed = " +
... ... @@ -1326,7 +1612,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1326 1612 shared.emplace_back(obj);
1327 1613 }
1328 1614 }
1329   - qpdf.no_ci_stop_if(
  1615 + no_ci_stop_if(
1330 1616 std::cmp_not_equal(
1331 1617 m->c_shared_object_data.nshared_total, m->c_shared_object_data.entries.size()),
1332 1618 "shared object hint table has wrong number of entries" //
... ... @@ -1337,7 +1623,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1337 1623 for (size_t i = 1; i < npages; ++i) {
1338 1624 CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i);
1339 1625 ObjUser ou(ObjUser::ou_page, i);
1340   - qpdf.no_ci_stop_if(
  1626 + no_ci_stop_if(
1341 1627 !m->obj_user_to_objects.contains(ou),
1342 1628 "found unreferenced page while calculating linearization data" //
1343 1629 );
... ... @@ -1420,12 +1706,12 @@ Lin::outputLengthNextN(
1420 1706  
1421 1707 int first = obj[in_object].renumber;
1422 1708 int last = first + n;
1423   - qpdf.no_ci_stop_if(
  1709 + no_ci_stop_if(
1424 1710 first <= 0, "found object that is not renumbered while writing linearization data");
1425 1711 qpdf_offset_t length = 0;
1426 1712 for (int i = first; i < last; ++i) {
1427 1713 auto l = new_obj[i].length;
1428   - qpdf.no_ci_stop_if(
  1714 + no_ci_stop_if(
1429 1715 l == 0, "found item with unknown length while writing linearization data" //
1430 1716 );
1431 1717 length += l;
... ... @@ -1440,8 +1726,8 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob
1440 1726  
1441 1727 // We are purposely leaving some values set to their initial zero values.
1442 1728  
1443   - std::vector<QPDFObjectHandle> const& pages = qpdf.getAllPages();
1444   - size_t npages = pages.size();
  1729 + auto const& all_pages = pages.all();
  1730 + size_t npages = all_pages.size();
1445 1731 CHPageOffset& cph = m->c_page_offset_data;
1446 1732 std::vector<CHPageOffsetEntry>& cphe = cph.entries;
1447 1733  
... ... @@ -1467,7 +1753,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob
1467 1753 // assignments.
1468 1754  
1469 1755 int nobjects = cphe.at(i).nobjects;
1470   - int length = outputLengthNextN(pages.at(i).getObjectID(), nobjects, new_obj, obj);
  1756 + int length = outputLengthNextN(all_pages.at(i).getObjectID(), nobjects, new_obj, obj);
1471 1757 int nshared = cphe.at(i).nshared_objects;
1472 1758  
1473 1759 min_nobjects = std::min(min_nobjects, nobjects);
... ... @@ -1483,7 +1769,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob
1483 1769 }
1484 1770  
1485 1771 ph.min_nobjects = min_nobjects;
1486   - ph.first_page_offset = new_obj[obj[pages.at(0)].renumber].xref.getOffset();
  1772 + ph.first_page_offset = new_obj[obj[all_pages.at(0)].renumber].xref.getOffset();
1487 1773 ph.nbits_delta_nobjects = nbits(max_nobjects - min_nobjects);
1488 1774 ph.min_page_length = min_length;
1489 1775 ph.nbits_delta_page_length = nbits(max_length - min_length);
... ... @@ -1502,7 +1788,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob
1502 1788 for (auto& phe_i: phe) {
1503 1789 // Adjust delta entries
1504 1790 if (phe_i.delta_nobjects < min_nobjects || phe_i.delta_page_length < min_length) {
1505   - qpdf.stopOnError(
  1791 + stopOnError(
1506 1792 "found too small delta nobjects or delta page length while writing "
1507 1793 "linearization data");
1508 1794 }
... ... @@ -1537,7 +1823,7 @@ Lin::calculateHSharedObject(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::
1537 1823 soe.emplace_back();
1538 1824 soe.at(i).delta_group_length = length;
1539 1825 }
1540   - qpdf.no_ci_stop_if(
  1826 + no_ci_stop_if(
1541 1827 soe.size() != toS(cso.nshared_total), "soe has wrong size after initialization" //
1542 1828 );
1543 1829  
... ... @@ -1553,7 +1839,7 @@ Lin::calculateHSharedObject(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::
1553 1839  
1554 1840 for (size_t i = 0; i < toS(cso.nshared_total); ++i) {
1555 1841 // Adjust deltas
1556   - qpdf.no_ci_stop_if(
  1842 + no_ci_stop_if(
1557 1843 soe.at(i).delta_group_length < min_length,
1558 1844 "found too small group length while writing linearization data" //
1559 1845 );
... ... @@ -1632,7 +1918,7 @@ Lin::writeHPageOffset(BitWriter&amp; w)
1632 1918 w.writeBitsInt(t.nbits_shared_numerator, 16); // 12
1633 1919 w.writeBitsInt(t.shared_denominator, 16); // 13
1634 1920  
1635   - int nitems = toI(qpdf.getAllPages().size());
  1921 + int nitems = toI(pages.size());
1636 1922 std::vector<HPageOffsetEntry>& entries = t.entries;
1637 1923  
1638 1924 write_vector_int(w, nitems, entries, t.nbits_delta_nobjects, &HPageOffsetEntry::delta_nobjects);
... ... @@ -1687,7 +1973,7 @@ Lin::writeHSharedObject(BitWriter&amp; w)
1687 1973 for (size_t i = 0; i < toS(nitems); ++i) {
1688 1974 // If signature were present, we'd have to write a 128-bit hash.
1689 1975 if (entries.at(i).signature_present != 0) {
1690   - qpdf.stopOnError("found unexpected signature present while writing linearization data");
  1976 + stopOnError("found unexpected signature present while writing linearization data");
1691 1977 }
1692 1978 }
1693 1979 write_vector_int(w, nitems, entries, t.nbits_nobjects, &HSharedObjectEntry::nobjects_minus_one);
... ...
libqpdf/QPDF_objects.cc
... ... @@ -123,7 +123,7 @@ Objects::parse(char const* password)
123 123 // Find the header anywhere in the first 1024 bytes of the file.
124 124 PatternFinder hf(qpdf, &QPDF::findHeader);
125 125 if (!m->file->findFirst("%PDF-", 0, 1024, hf)) {
126   - qpdf.warn(qpdf.damagedPDF("", -1, "can't find PDF header"));
  126 + warn(damagedPDF("", -1, "can't find PDF header"));
127 127 // QPDFWriter writes files that usually require at least version 1.2 for /FlateDecode
128 128 m->pdf_version = "1.2";
129 129 }
... ... @@ -147,14 +147,14 @@ Objects::parse(char const* password)
147 147  
148 148 try {
149 149 if (xref_offset == 0) {
150   - throw qpdf.damagedPDF("", -1, "can't find startxref");
  150 + throw damagedPDF("", -1, "can't find startxref");
151 151 }
152 152 try {
153 153 read_xref(xref_offset);
154 154 } catch (QPDFExc&) {
155 155 throw;
156 156 } catch (std::exception& e) {
157   - throw qpdf.damagedPDF("", -1, std::string("error reading xref: ") + e.what());
  157 + throw damagedPDF("", -1, std::string("error reading xref: ") + e.what());
158 158 }
159 159 } catch (QPDFExc& e) {
160 160 if (m->attempt_recovery) {
... ... @@ -168,7 +168,7 @@ Objects::parse(char const* password)
168 168 m->parsed = true;
169 169 if (!m->xref_table.empty() && !qpdf.getRoot().getKey("/Pages").isDictionary()) {
170 170 // QPDFs created from JSON have an empty xref table and no root object yet.
171   - throw qpdf.damagedPDF("", -1, "unable to find page tree");
  171 + throw damagedPDF("", -1, "unable to find page tree");
172 172 }
173 173 }
174 174  
... ... @@ -208,8 +208,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
208 208 const auto max_warnings = m->warnings.size() + 1000U;
209 209 auto check_warnings = [this, max_warnings]() {
210 210 if (m->warnings.size() > max_warnings) {
211   - throw qpdf.damagedPDF(
212   - "", -1, "too many errors while reconstructing cross-reference table");
  211 + throw damagedPDF("", -1, "too many errors while reconstructing cross-reference table");
213 212 }
214 213 };
215 214  
... ... @@ -217,9 +216,9 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
217 216 // We may find more objects, which may contain dangling references.
218 217 m->fixed_dangling_refs = false;
219 218  
220   - qpdf.warn(qpdf.damagedPDF("", -1, "file is damaged"));
221   - qpdf.warn(e);
222   - qpdf.warn(qpdf.damagedPDF("", -1, "Attempting to reconstruct cross-reference table"));
  219 + warn(damagedPDF("", -1, "file is damaged"));
  220 + warn(e);
  221 + warn(damagedPDF("", -1, "Attempting to reconstruct cross-reference table"));
223 222  
224 223 // Delete all references to type 1 (uncompressed) objects
225 224 std::vector<QPDFObjGen> to_delete;
... ... @@ -253,7 +252,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
253 252 if (obj <= m->xref_table_max_id) {
254 253 found_objects.emplace_back(obj, gen, token_start);
255 254 } else {
256   - qpdf.warn(qpdf.damagedPDF(
  255 + warn(damagedPDF(
257 256 "", -1, "ignoring object with impossibly large id " + std::to_string(obj)));
258 257 }
259 258 }
... ... @@ -278,7 +277,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
278 277  
279 278 if (qpdf.getRoot().getKey("/Pages").isDictionary()) {
280 279 QTC::TC("qpdf", "QPDF startxref more than 1024 before end");
281   - qpdf.warn(qpdf.damagedPDF(
  280 + warn(damagedPDF(
282 281 "", -1, "startxref was more than 1024 bytes before end of file"));
283 282 qpdf.initializeEncryption();
284 283 m->parsed = true;
... ... @@ -313,7 +312,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
313 312 m->trailer = t;
314 313 break;
315 314 }
316   - qpdf.warn(qpdf.damagedPDF("trailer", *it, "recovered trailer has no /Root entry"));
  315 + warn(damagedPDF("trailer", *it, "recovered trailer has no /Root entry"));
317 316 }
318 317 check_warnings();
319 318 }
... ... @@ -347,7 +346,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
347 346 try {
348 347 read_xref(max_offset, true);
349 348 } catch (std::exception&) {
350   - qpdf.warn(qpdf.damagedPDF(
  349 + warn(damagedPDF(
351 350 "", -1, "error decoding candidate xref stream while recovering damaged file"));
352 351 }
353 352 QTC::TC("qpdf", "QPDF recover xref stream");
... ... @@ -368,7 +367,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
368 367 }
369 368 if (root) {
370 369 if (!m->trailer) {
371   - qpdf.warn(qpdf.damagedPDF(
  370 + warn(damagedPDF(
372 371 "", -1, "unable to find trailer dictionary while recovering damaged file"));
373 372 m->trailer = QPDFObjectHandle::newDictionary();
374 373 }
... ... @@ -381,23 +380,20 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
381 380 // could try to get the trailer from there. This may make it possible to recover files with
382 381 // bad startxref pointers even when they have object streams.
383 382  
384   - throw qpdf.damagedPDF(
385   - "", -1, "unable to find trailer dictionary while recovering damaged file");
  383 + throw damagedPDF("", -1, "unable to find trailer dictionary while recovering damaged file");
386 384 }
387 385 if (m->xref_table.empty()) {
388 386 // We cannot check for an empty xref table in parse because empty tables are valid when
389 387 // creating QPDF objects from JSON.
390   - throw qpdf.damagedPDF("", -1, "unable to find objects while recovering damaged file");
  388 + throw damagedPDF("", -1, "unable to find objects while recovering damaged file");
391 389 }
392 390 check_warnings();
393 391 if (!m->parsed) {
394   - m->parsed = true;
395   - qpdf.getAllPages();
396   - check_warnings();
397   - if (m->all_pages.empty()) {
398   - m->parsed = false;
399   - throw qpdf.damagedPDF("", -1, "unable to find any pages while recovering damaged file");
  392 + m->parsed = !m->pages.empty();
  393 + if (!m->parsed) {
  394 + throw damagedPDF("", -1, "unable to find any pages while recovering damaged file");
400 395 }
  396 + check_warnings();
401 397 }
402 398  
403 399 // We could iterate through the objects looking for streams and try to find objects inside of
... ... @@ -443,7 +439,7 @@ Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
443 439 // where it is terminated by arbitrary whitespace.
444 440 if ((strncmp(buf, "xref", 4) == 0) && util::is_space(buf[4])) {
445 441 if (skipped_space) {
446   - qpdf.warn(qpdf.damagedPDF("", -1, "extraneous whitespace seen before xref"));
  442 + warn(damagedPDF("", -1, "extraneous whitespace seen before xref"));
447 443 }
448 444 QTC::TC(
449 445 "qpdf",
... ... @@ -462,12 +458,12 @@ Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
462 458 xref_offset = read_xrefStream(xref_offset, in_stream_recovery);
463 459 }
464 460 if (visited.contains(xref_offset)) {
465   - throw qpdf.damagedPDF("", -1, "loop detected following xref tables");
  461 + throw damagedPDF("", -1, "loop detected following xref tables");
466 462 }
467 463 }
468 464  
469 465 if (!m->trailer) {
470   - throw qpdf.damagedPDF("", -1, "unable to find trailer while reading xref");
  466 + throw damagedPDF("", -1, "unable to find trailer while reading xref");
471 467 }
472 468 int size = m->trailer.getKey("/Size").getIntValueAsInt();
473 469 int max_obj = 0;
... ... @@ -478,7 +474,7 @@ Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
478 474 max_obj = std::max(max_obj, *(m->deleted_objects.rbegin()));
479 475 }
480 476 if ((size < 1) || (size - 1 != max_obj)) {
481   - qpdf.warn(qpdf.damagedPDF(
  477 + warn(damagedPDF(
482 478 "",
483 479 -1,
484 480 ("reported number of objects (" + std::to_string(size) +
... ... @@ -615,7 +611,7 @@ Objects::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
615 611 }
616 612  
617 613 if (invalid) {
618   - qpdf.warn(qpdf.damagedPDF("xref table", "accepting invalid xref table entry"));
  614 + warn(damagedPDF("xref table", "accepting invalid xref table entry"));
619 615 }
620 616  
621 617 f1 = QUtil::string_to_ll(f1_str.c_str());
... ... @@ -692,7 +688,7 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset)
692 688 int num = 0;
693 689 int bytes = 0;
694 690 if (!parse_xrefFirst(line, obj, num, bytes)) {
695   - throw qpdf.damagedPDF("xref table", "xref syntax invalid");
  691 + throw damagedPDF("xref table", "xref syntax invalid");
696 692 }
697 693 m->file->seek(m->file->getLastOffset() + bytes, SEEK_SET);
698 694 for (qpdf_offset_t i = obj; i - num < obj; ++i) {
... ... @@ -705,7 +701,7 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset)
705 701 int f2 = 0;
706 702 char type = '\0';
707 703 if (!read_xrefEntry(f1, f2, type)) {
708   - throw qpdf.damagedPDF(
  704 + throw damagedPDF(
709 705 "xref table", "invalid xref entry (obj=" + std::to_string(i) + ")");
710 706 }
711 707 if (type == 'f') {
... ... @@ -725,17 +721,17 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset)
725 721 // Set offset to previous xref table if any
726 722 QPDFObjectHandle cur_trailer = m->objects.readTrailer();
727 723 if (!cur_trailer.isDictionary()) {
728   - throw qpdf.damagedPDF("", "expected trailer dictionary");
  724 + throw damagedPDF("", "expected trailer dictionary");
729 725 }
730 726  
731 727 if (!m->trailer) {
732 728 setTrailer(cur_trailer);
733 729  
734 730 if (!m->trailer.hasKey("/Size")) {
735   - throw qpdf.damagedPDF("trailer", "trailer dictionary lacks /Size key");
  731 + throw damagedPDF("trailer", "trailer dictionary lacks /Size key");
736 732 }
737 733 if (!m->trailer.getKey("/Size").isInteger()) {
738   - throw qpdf.damagedPDF("trailer", "/Size key in trailer dictionary is not an integer");
  734 + throw damagedPDF("trailer", "/Size key in trailer dictionary is not an integer");
739 735 }
740 736 }
741 737  
... ... @@ -748,14 +744,14 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset)
748 744 // /Prev key instead of the xref stream's.
749 745 (void)read_xrefStream(cur_trailer.getKey("/XRefStm").getIntValue());
750 746 } else {
751   - throw qpdf.damagedPDF("xref stream", xref_offset, "invalid /XRefStm");
  747 + throw damagedPDF("xref stream", xref_offset, "invalid /XRefStm");
752 748 }
753 749 }
754 750 }
755 751  
756 752 if (cur_trailer.hasKey("/Prev")) {
757 753 if (!cur_trailer.getKey("/Prev").isInteger()) {
758   - throw qpdf.damagedPDF("trailer", "/Prev key in trailer dictionary is not an integer");
  754 + throw damagedPDF("trailer", "/Prev key in trailer dictionary is not an integer");
759 755 }
760 756 return cur_trailer.getKey("/Prev").getIntValue();
761 757 }
... ... @@ -781,7 +777,7 @@ Objects::read_xrefStream(qpdf_offset_t xref_offset, bool in_stream_recovery)
781 777 }
782 778 }
783 779  
784   - throw qpdf.damagedPDF("", xref_offset, "xref not found");
  780 + throw damagedPDF("", xref_offset, "xref not found");
785 781 return 0; // unreachable
786 782 }
787 783  
... ... @@ -912,7 +908,7 @@ Objects::processXRefStream(
912 908 qpdf_offset_t xref_offset, QPDFObjectHandle& xref_obj, bool in_stream_recovery)
913 909 {
914 910 auto damaged = [this, xref_offset](std::string_view msg) -> QPDFExc {
915   - return qpdf.damagedPDF("xref stream", xref_offset, msg.data());
  911 + return damagedPDF("xref stream", xref_offset, msg.data());
916 912 };
917 913  
918 914 auto dict = xref_obj.getDict();
... ... @@ -932,7 +928,7 @@ Objects::processXRefStream(
932 928 if (expected_size > actual_size) {
933 929 throw x;
934 930 } else {
935   - qpdf.warn(x);
  931 + warn(x);
936 932 }
937 933 }
938 934  
... ... @@ -992,7 +988,7 @@ Objects::processXRefStream(
992 988  
993 989 if (dict.hasKey("/Prev")) {
994 990 if (!dict.getKey("/Prev").isInteger()) {
995   - throw qpdf.damagedPDF(
  991 + throw damagedPDF(
996 992 "xref stream", "/Prev key in xref stream dictionary is not an integer");
997 993 }
998 994 return dict.getKey("/Prev").getIntValue();
... ... @@ -1030,13 +1026,13 @@ Objects::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1030 1026  
1031 1027 if (f0 == 2) {
1032 1028 if (f1 == obj) {
1033   - qpdf.warn(qpdf.damagedPDF(
1034   - "xref stream", "self-referential object stream " + std::to_string(obj)));
  1029 + warn(
  1030 + damagedPDF("xref stream", "self-referential object stream " + std::to_string(obj)));
1035 1031 return;
1036 1032 }
1037 1033 if (f1 > m->xref_table_max_id) {
1038 1034 // ignore impossibly large object stream ids
1039   - qpdf.warn(qpdf.damagedPDF(
  1035 + warn(damagedPDF(
1040 1036 "xref stream",
1041 1037 "object stream id " + std::to_string(f1) + " for object " + std::to_string(obj) +
1042 1038 " is impossibly large"));
... ... @@ -1061,8 +1057,7 @@ Objects::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1061 1057 break;
1062 1058  
1063 1059 default:
1064   - throw qpdf.damagedPDF(
1065   - "xref stream", "unknown xref stream entry type " + std::to_string(f0));
  1060 + throw damagedPDF("xref stream", "unknown xref stream entry type " + std::to_string(f0));
1066 1061 break;
1067 1062 }
1068 1063 }
... ... @@ -1182,9 +1177,9 @@ Objects::readTrailer()
1182 1177 if (empty) {
1183 1178 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1184 1179 // actual PDF files and Adobe Reader appears to ignore them.
1185   - qpdf.warn(qpdf.damagedPDF("trailer", "empty object treated as null"));
  1180 + warn(damagedPDF("trailer", "empty object treated as null"));
1186 1181 } else if (object.isDictionary() && m->objects.readToken(*m->file).isWord("stream")) {
1187   - qpdf.warn(qpdf.damagedPDF("trailer", m->file->tell(), "stream keyword found in trailer"));
  1182 + warn(damagedPDF("trailer", m->file->tell(), "stream keyword found in trailer"));
1188 1183 }
1189 1184 // Override last_offset so that it points to the beginning of the object we just read
1190 1185 m->file->setLastOffset(offset);
... ... @@ -1210,8 +1205,7 @@ Objects::readObject(std::string const&amp; description, QPDFObjGen og)
1210 1205 if (empty) {
1211 1206 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1212 1207 // actual PDF files and Adobe Reader appears to ignore them.
1213   - qpdf.warn(
1214   - qpdf.damagedPDF(*m->file, m->file->getLastOffset(), "empty object treated as null"));
  1208 + warn(damagedPDF(*m->file, m->file->getLastOffset(), "empty object treated as null"));
1215 1209 return object;
1216 1210 }
1217 1211 auto token = readToken(*m->file);
... ... @@ -1220,7 +1214,7 @@ Objects::readObject(std::string const&amp; description, QPDFObjGen og)
1220 1214 token = readToken(*m->file);
1221 1215 }
1222 1216 if (!token.isWord("endobj")) {
1223   - qpdf.warn(qpdf.damagedPDF("expected endobj"));
  1217 + warn(damagedPDF("expected endobj"));
1224 1218 }
1225 1219 return object;
1226 1220 }
... ... @@ -1241,9 +1235,9 @@ Objects::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offse
1241 1235  
1242 1236 if (!length_obj.isInteger()) {
1243 1237 if (length_obj.null()) {
1244   - throw qpdf.damagedPDF(offset, "stream dictionary lacks /Length key");
  1238 + throw damagedPDF(offset, "stream dictionary lacks /Length key");
1245 1239 }
1246   - throw qpdf.damagedPDF(offset, "/Length key in stream dictionary is not an integer");
  1240 + throw damagedPDF(offset, "/Length key in stream dictionary is not an integer");
1247 1241 }
1248 1242  
1249 1243 length = toS(length_obj.getUIntValue());
... ... @@ -1251,11 +1245,11 @@ Objects::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offse
1251 1245 m->file->seek(stream_offset, SEEK_SET);
1252 1246 m->file->seek(toO(length), SEEK_CUR);
1253 1247 if (!readToken(*m->file).isWord("endstream")) {
1254   - throw qpdf.damagedPDF("expected endstream");
  1248 + throw damagedPDF("expected endstream");
1255 1249 }
1256 1250 } catch (QPDFExc& e) {
1257 1251 if (m->attempt_recovery) {
1258   - qpdf.warn(e);
  1252 + warn(e);
1259 1253 length = recoverStreamLength(m->file, og, stream_offset);
1260 1254 } else {
1261 1255 throw;
... ... @@ -1295,7 +1289,7 @@ Objects::validateStreamLineEnd(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_off
1295 1289 // Treat the \r by itself as the whitespace after endstream and start reading
1296 1290 // stream data in spite of not having seen a newline.
1297 1291 m->file->unreadCh(ch);
1298   - qpdf.warn(qpdf.damagedPDF(
  1292 + warn(damagedPDF(
1299 1293 m->file->tell(), "stream keyword followed by carriage return only"));
1300 1294 }
1301 1295 }
... ... @@ -1303,12 +1297,11 @@ Objects::validateStreamLineEnd(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_off
1303 1297 }
1304 1298 if (!util::is_space(ch)) {
1305 1299 m->file->unreadCh(ch);
1306   - qpdf.warn(qpdf.damagedPDF(
  1300 + warn(damagedPDF(
1307 1301 m->file->tell(), "stream keyword not followed by proper line terminator"));
1308 1302 return;
1309 1303 }
1310   - qpdf.warn(
1311   - qpdf.damagedPDF(m->file->tell(), "stream keyword followed by extraneous whitespace"));
  1304 + warn(damagedPDF(m->file->tell(), "stream keyword followed by extraneous whitespace"));
1312 1305 }
1313 1306 }
1314 1307  
... ... @@ -1319,7 +1312,7 @@ Objects::readObjectInStream(is::OffsetBuffer&amp; input, int stream_id, int obj_id)
1319 1312 if (empty) {
1320 1313 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1321 1314 // actual PDF files and Adobe Reader appears to ignore them.
1322   - qpdf.warn(QPDFExc(
  1315 + warn(QPDFExc(
1323 1316 qpdf_e_damaged_pdf,
1324 1317 m->file->getName() + " object stream " + std::to_string(stream_id),
1325 1318 +"object " + std::to_string(obj_id) + " 0, offset " +
... ... @@ -1347,7 +1340,7 @@ Objects::recoverStreamLength(
1347 1340 std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset)
1348 1341 {
1349 1342 // Try to reconstruct stream length by looking for endstream or endobj
1350   - qpdf.warn(qpdf.damagedPDF(*input, stream_offset, "attempting to recover stream length"));
  1343 + warn(damagedPDF(*input, stream_offset, "attempting to recover stream length"));
1351 1344  
1352 1345 PatternFinder ef(qpdf, &QPDF::findEndstream);
1353 1346 size_t length = 0;
... ... @@ -1386,10 +1379,10 @@ Objects::recoverStreamLength(
1386 1379 }
1387 1380  
1388 1381 if (length == 0) {
1389   - qpdf.warn(qpdf.damagedPDF(
  1382 + warn(damagedPDF(
1390 1383 *input, stream_offset, "unable to recover stream data; treating stream as empty"));
1391 1384 } else {
1392   - qpdf.warn(qpdf.damagedPDF(
  1385 + warn(damagedPDF(
1393 1386 *input, stream_offset, "recovered stream length: " + std::to_string(length)));
1394 1387 }
1395 1388  
... ... @@ -1409,24 +1402,24 @@ Objects::read_object_start(qpdf_offset_t offset)
1409 1402 QPDFTokenizer::Token tobjid = readToken(*m->file);
1410 1403 bool objidok = tobjid.isInteger();
1411 1404 if (!objidok) {
1412   - throw qpdf.damagedPDF(offset, "expected n n obj");
  1405 + throw damagedPDF(offset, "expected n n obj");
1413 1406 }
1414 1407 QPDFTokenizer::Token tgen = readToken(*m->file);
1415 1408 bool genok = tgen.isInteger();
1416 1409 if (!genok) {
1417   - throw qpdf.damagedPDF(offset, "expected n n obj");
  1410 + throw damagedPDF(offset, "expected n n obj");
1418 1411 }
1419 1412 QPDFTokenizer::Token tobj = readToken(*m->file);
1420 1413  
1421 1414 bool objok = tobj.isWord("obj");
1422 1415  
1423 1416 if (!objok) {
1424   - throw qpdf.damagedPDF(offset, "expected n n obj");
  1417 + throw damagedPDF(offset, "expected n n obj");
1425 1418 }
1426 1419 int objid = QUtil::string_to_int(tobjid.getValue().c_str());
1427 1420 int generation = QUtil::string_to_int(tgen.getValue().c_str());
1428 1421 if (objid == 0) {
1429   - throw qpdf.damagedPDF(offset, "object with ID 0");
  1422 + throw damagedPDF(offset, "object with ID 0");
1430 1423 }
1431 1424 return {objid, generation};
1432 1425 }
... ... @@ -1447,20 +1440,20 @@ Objects::readObjectAtOffset(
1447 1440 // "0000000000 00000 n", which is not correct, but it won't hurt anything for us to ignore
1448 1441 // these.
1449 1442 if (offset == 0) {
1450   - qpdf.warn(qpdf.damagedPDF(-1, "object has offset 0"));
  1443 + warn(damagedPDF(-1, "object has offset 0"));
1451 1444 return;
1452 1445 }
1453 1446  
1454 1447 try {
1455 1448 og = read_object_start(offset);
1456 1449 if (exp_og != og) {
1457   - QPDFExc e = qpdf.damagedPDF(offset, "expected " + exp_og.unparse(' ') + " obj");
  1450 + QPDFExc e = damagedPDF(offset, "expected " + exp_og.unparse(' ') + " obj");
1458 1451 if (try_recovery) {
1459 1452 // Will be retried below
1460 1453 throw e;
1461 1454 } else {
1462 1455 // We can try reading the object anyway even if the ID doesn't match.
1463   - qpdf.warn(e);
  1456 + warn(e);
1464 1457 }
1465 1458 }
1466 1459 } catch (QPDFExc& e) {
... ... @@ -1474,7 +1467,7 @@ Objects::readObjectAtOffset(
1474 1467 readObjectAtOffset(false, new_offset, description, exp_og);
1475 1468 return;
1476 1469 }
1477   - qpdf.warn(qpdf.damagedPDF(
  1470 + warn(damagedPDF(
1478 1471 "",
1479 1472 -1,
1480 1473 ("object " + exp_og.unparse(' ') +
... ... @@ -1493,7 +1486,7 @@ Objects::readObjectAtOffset(
1493 1486 while (true) {
1494 1487 char ch;
1495 1488 if (!m->file->read(&ch, 1)) {
1496   - throw qpdf.damagedPDF(m->file->tell(), "EOF after endobj");
  1489 + throw damagedPDF(m->file->tell(), "EOF after endobj");
1497 1490 }
1498 1491 if (!isspace(static_cast<unsigned char>(ch))) {
1499 1492 m->file->seek(-1, SEEK_CUR);
... ... @@ -1552,7 +1545,7 @@ Objects::readObjectAtOffset(
1552 1545 while (true) {
1553 1546 char ch;
1554 1547 if (!m->file->read(&ch, 1)) {
1555   - throw qpdf.damagedPDF(m->file->tell(), "EOF after endobj");
  1548 + throw damagedPDF(m->file->tell(), "EOF after endobj");
1556 1549 }
1557 1550 if (!isspace(static_cast<unsigned char>(ch))) {
1558 1551 m->file->seek(-1, SEEK_CUR);
... ... @@ -1574,7 +1567,7 @@ Objects::resolve(QPDFObjGen og)
1574 1567 if (m->resolving.contains(og)) {
1575 1568 // This can happen if an object references itself directly or indirectly in some key that
1576 1569 // has to be resolved during object parsing, such as stream length.
1577   - qpdf.warn(qpdf.damagedPDF("", "loop detected resolving object " + og.unparse(' ')));
  1570 + warn(damagedPDF("", "loop detected resolving object " + og.unparse(' ')));
1578 1571 updateCache(og, QPDFObject::create<QPDF_Null>(), -1, -1);
1579 1572 return m->obj_cache[og].object;
1580 1573 }
... ... @@ -1594,13 +1587,13 @@ Objects::resolve(QPDFObjGen og)
1594 1587 break;
1595 1588  
1596 1589 default:
1597   - throw qpdf.damagedPDF(
  1590 + throw damagedPDF(
1598 1591 "", -1, ("object " + og.unparse('/') + " has unexpected xref entry type"));
1599 1592 }
1600 1593 } catch (QPDFExc& e) {
1601   - qpdf.warn(e);
  1594 + warn(e);
1602 1595 } catch (std::exception& e) {
1603   - qpdf.warn(qpdf.damagedPDF(
  1596 + warn(damagedPDF(
1604 1597 "", -1, ("object " + og.unparse('/') + ": error reading object: " + e.what())));
1605 1598 }
1606 1599 }
... ... @@ -1636,7 +1629,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1636 1629 // Force resolution of object stream
1637 1630 Stream obj_stream = qpdf.getObject(obj_stream_number, 0);
1638 1631 if (!obj_stream) {
1639   - throw qpdf.damagedPDF(
  1632 + throw damagedPDF(
1640 1633 "object " + std::to_string(obj_stream_number) + " 0",
1641 1634 "supposed object stream " + std::to_string(obj_stream_number) + " is not a stream");
1642 1635 }
... ... @@ -1649,7 +1642,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1649 1642  
1650 1643 QPDFObjectHandle dict = obj_stream.getDict();
1651 1644 if (!dict.isDictionaryOfType("/ObjStm")) {
1652   - qpdf.warn(qpdf.damagedPDF(
  1645 + warn(damagedPDF(
1653 1646 "object " + std::to_string(obj_stream_number) + " 0",
1654 1647 "supposed object stream " + std::to_string(obj_stream_number) + " has wrong type"));
1655 1648 }
... ... @@ -1657,7 +1650,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1657 1650 unsigned int n{0};
1658 1651 int first{0};
1659 1652 if (!(dict.getKey("/N").getValueAsUInt(n) && dict.getKey("/First").getValueAsInt(first))) {
1660   - throw qpdf.damagedPDF(
  1653 + throw damagedPDF(
1661 1654 "object " + std::to_string(obj_stream_number) + " 0",
1662 1655 "object stream " + std::to_string(obj_stream_number) + " has incorrect keys");
1663 1656 }
... ... @@ -1674,7 +1667,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1674 1667 auto b_start = stream_data.data();
1675 1668  
1676 1669 if (first >= end_offset) {
1677   - throw qpdf.damagedPDF(
  1670 + throw damagedPDF(
1678 1671 "object " + std::to_string(obj_stream_number) + " 0",
1679 1672 "object stream " + std::to_string(obj_stream_number) + " has invalid /First entry");
1680 1673 }
... ... @@ -1694,17 +1687,17 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1694 1687 long long offset = QUtil::string_to_int(toffset.getValue().c_str());
1695 1688  
1696 1689 if (num == obj_stream_number) {
1697   - qpdf.warn(damaged(num, id_offset, "object stream claims to contain itself"));
  1690 + warn(damaged(num, id_offset, "object stream claims to contain itself"));
1698 1691 continue;
1699 1692 }
1700 1693  
1701 1694 if (num < 1) {
1702   - qpdf.warn(damaged(num, id_offset, "object id is invalid"s));
  1695 + warn(damaged(num, id_offset, "object id is invalid"s));
1703 1696 continue;
1704 1697 }
1705 1698  
1706 1699 if (offset <= last_offset) {
1707   - qpdf.warn(damaged(
  1700 + warn(damaged(
1708 1701 num,
1709 1702 input.getLastOffset(),
1710 1703 "offset " + std::to_string(offset) +
... ... @@ -1718,7 +1711,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1718 1711 }
1719 1712  
1720 1713 if (first + offset >= end_offset) {
1721   - qpdf.warn(damaged(
  1714 + warn(damaged(
1722 1715 num, input.getLastOffset(), "offset " + std::to_string(offset) + " is too large"));
1723 1716 continue;
1724 1717 }
... ... @@ -1934,7 +1927,7 @@ Objects::tableSize()
1934 1927 // Temporary fix. Long-term solution is
1935 1928 // - QPDFObjGen to enforce objgens are valid and sensible
1936 1929 // - xref table and obj cache to protect against insertion of impossibly large obj ids
1937   - qpdf.stopOnError("Impossibly large object id encountered.");
  1930 + stopOnError("Impossibly large object id encountered.");
1938 1931 }
1939 1932 if (max_obj < 1.1 * std::max(toI(m->obj_cache.size()), max_xref)) {
1940 1933 return toS(++max_obj);
... ...
libqpdf/QPDF_optimization.cc deleted
1   -// See the "Optimization" section of the manual.
2   -
3   -#include <qpdf/QPDF_private.hh>
4   -
5   -#include <qpdf/QPDFExc.hh>
6   -#include <qpdf/QPDFObjectHandle_private.hh>
7   -#include <qpdf/QPDFWriter_private.hh>
8   -#include <qpdf/QTC.hh>
9   -
10   -using Lin = QPDF::Doc::Linearization;
11   -using Pages = QPDF::Doc::Pages;
12   -
13   -QPDF::ObjUser::ObjUser(user_e type) :
14   - ou_type(type)
15   -{
16   - qpdf_assert_debug(type == ou_root);
17   -}
18   -
19   -QPDF::ObjUser::ObjUser(user_e type, size_t pageno) :
20   - ou_type(type),
21   - pageno(pageno)
22   -{
23   - qpdf_assert_debug((type == ou_page) || (type == ou_thumb));
24   -}
25   -
26   -QPDF::ObjUser::ObjUser(user_e type, std::string const& key) :
27   - ou_type(type),
28   - key(key)
29   -{
30   - qpdf_assert_debug((type == ou_trailer_key) || (type == ou_root_key));
31   -}
32   -
33   -bool
34   -QPDF::ObjUser::operator<(ObjUser const& rhs) const
35   -{
36   - if (ou_type < rhs.ou_type) {
37   - return true;
38   - }
39   - if (ou_type == rhs.ou_type) {
40   - if (pageno < rhs.pageno) {
41   - return true;
42   - }
43   - if (pageno == rhs.pageno) {
44   - return key < rhs.key;
45   - }
46   - }
47   - return false;
48   -}
49   -
50   -QPDF::UpdateObjectMapsFrame::UpdateObjectMapsFrame(
51   - QPDF::ObjUser const& ou, QPDFObjectHandle oh, bool top) :
52   - ou(ou),
53   - oh(oh),
54   - top(top)
55   -{
56   -}
57   -
58   -void
59   -QPDF::optimize(
60   - std::map<int, int> const& object_stream_data,
61   - bool allow_changes,
62   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
63   -{
64   - m->lin.optimize_internal(object_stream_data, allow_changes, skip_stream_parameters);
65   -}
66   -
67   -void
68   -Lin::optimize(
69   - QPDFWriter::ObjTable const& obj, std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
70   -{
71   - optimize_internal(obj, true, skip_stream_parameters);
72   -}
73   -
74   -template <typename T>
75   -void
76   -Lin::optimize_internal(
77   - T const& object_stream_data,
78   - bool allow_changes,
79   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
80   -{
81   - if (!m->obj_user_to_objects.empty()) {
82   - // already optimized
83   - return;
84   - }
85   -
86   - // The PDF specification indicates that /Outlines is supposed to be an indirect reference. Force
87   - // it to be so if it exists and is direct. (This has been seen in the wild.)
88   - QPDFObjectHandle root = qpdf.getRoot();
89   - if (root.getKey("/Outlines").isDictionary()) {
90   - QPDFObjectHandle outlines = root.getKey("/Outlines");
91   - if (!outlines.isIndirect()) {
92   - root.replaceKey("/Outlines", qpdf.makeIndirectObject(outlines));
93   - }
94   - }
95   -
96   - // Traverse pages tree pushing all inherited resources down to the page level. This also
97   - // initializes m->all_pages.
98   - m->pages.pushInheritedAttributesToPage(allow_changes, false);
99   -
100   - // Traverse pages
101   - size_t n = m->all_pages.size();
102   - for (size_t pageno = 0; pageno < n; ++pageno) {
103   - updateObjectMaps(
104   - ObjUser(ObjUser::ou_page, pageno), m->all_pages.at(pageno), skip_stream_parameters);
105   - }
106   -
107   - // Traverse document-level items
108   - for (auto const& [key, value]: m->trailer.as_dictionary()) {
109   - if (key == "/Root") {
110   - // handled separately
111   - } else {
112   - if (!value.null()) {
113   - updateObjectMaps(
114   - ObjUser(ObjUser::ou_trailer_key, key), value, skip_stream_parameters);
115   - }
116   - }
117   - }
118   -
119   - for (auto const& [key, value]: root.as_dictionary()) {
120   - // Technically, /I keys from /Thread dictionaries are supposed to be handled separately, but
121   - // we are going to disregard that specification for now. There is loads of evidence that
122   - // pdlin and Acrobat both disregard things like this from time to time, so this is almost
123   - // certain not to cause any problems.
124   - if (!value.null()) {
125   - updateObjectMaps(ObjUser(ObjUser::ou_root_key, key), value, skip_stream_parameters);
126   - }
127   - }
128   -
129   - ObjUser root_ou = ObjUser(ObjUser::ou_root);
130   - auto root_og = QPDFObjGen(root.getObjGen());
131   - m->obj_user_to_objects[root_ou].insert(root_og);
132   - m->object_to_obj_users[root_og].insert(root_ou);
133   -
134   - filterCompressedObjects(object_stream_data);
135   -}
136   -
137   -void
138   -QPDF::pushInheritedAttributesToPage()
139   -{
140   - // Public API should not have access to allow_changes.
141   - m->pages.pushInheritedAttributesToPage(true, false);
142   -}
143   -
144   -void
145   -Pages::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
146   -{
147   - // Traverse pages tree pushing all inherited resources down to the page level.
148   -
149   - // The record of whether we've done this is cleared by updateAllPagesCache(). If we're warning
150   - // for skipped keys, re-traverse unconditionally.
151   - if (m->pushed_inherited_attributes_to_pages && (!warn_skipped_keys)) {
152   - return;
153   - }
154   -
155   - // Calling getAllPages() resolves any duplicated page objects, repairs broken nodes, and detects
156   - // loops, so we don't have to do those activities here.
157   - qpdf.getAllPages();
158   -
159   - // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain
160   - // values for them.
161   - std::map<std::string, std::vector<QPDFObjectHandle>> key_ancestors;
162   - pushInheritedAttributesToPageInternal(
163   - m->trailer.getKey("/Root").getKey("/Pages"),
164   - key_ancestors,
165   - allow_changes,
166   - warn_skipped_keys);
167   - if (!key_ancestors.empty()) {
168   - throw std::logic_error(
169   - "key_ancestors not empty after pushing inherited attributes to pages");
170   - }
171   - m->pushed_inherited_attributes_to_pages = true;
172   - m->ever_pushed_inherited_attributes_to_pages = true;
173   -}
174   -
175   -void
176   -Pages ::pushInheritedAttributesToPageInternal(
177   - QPDFObjectHandle cur_pages,
178   - std::map<std::string, std::vector<QPDFObjectHandle>>& key_ancestors,
179   - bool allow_changes,
180   - bool warn_skipped_keys)
181   -{
182   - // Make a list of inheritable keys. Only the keys /MediaBox, /CropBox, /Resources, and /Rotate
183   - // are inheritable attributes. Push this object onto the stack of pages nodes that have values
184   - // for this attribute.
185   -
186   - std::set<std::string> inheritable_keys;
187   - for (auto const& key: cur_pages.getKeys()) {
188   - if (key == "/MediaBox" || key == "/CropBox" || key == "/Resources" || key == "/Rotate") {
189   - if (!allow_changes) {
190   - throw QPDFExc(
191   - qpdf_e_internal,
192   - m->file->getName(),
193   - m->last_object_description,
194   - m->file->getLastOffset(),
195   - "optimize detected an inheritable attribute when called in no-change mode");
196   - }
197   -
198   - // This is an inheritable resource
199   - inheritable_keys.insert(key);
200   - QPDFObjectHandle oh = cur_pages.getKey(key);
201   - QTC::TC("qpdf", "QPDF opt direct pages resource", oh.indirect() ? 0 : 1);
202   - if (!oh.indirect()) {
203   - if (!oh.isScalar()) {
204   - // Replace shared direct object non-scalar resources with indirect objects to
205   - // avoid copying large structures around.
206   - cur_pages.replaceKey(key, qpdf.makeIndirectObject(oh));
207   - oh = cur_pages.getKey(key);
208   - } else {
209   - // It's okay to copy scalars.
210   - }
211   - }
212   - key_ancestors[key].push_back(oh);
213   - if (key_ancestors[key].size() > 1) {
214   - }
215   - // Remove this resource from this node. It will be reattached at the page level.
216   - cur_pages.removeKey(key);
217   - } else if (!(key == "/Type" || key == "/Parent" || key == "/Kids" || key == "/Count")) {
218   - // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not
219   - // set), as we don't change these; but flattening removes intermediate /Pages nodes.
220   - if (warn_skipped_keys && cur_pages.hasKey("/Parent")) {
221   - qpdf.warn(
222   - qpdf_e_pages,
223   - "Pages object: object " + cur_pages.id_gen().unparse(' '),
224   - 0,
225   - ("Unknown key " + key +
226   - " in /Pages object is being discarded as a result of flattening the /Pages "
227   - "tree"));
228   - }
229   - }
230   - }
231   -
232   - // Process descendant nodes. This method does not perform loop detection because all code paths
233   - // that lead here follow a call to getAllPages, which already throws an exception in the event
234   - // of a loop in the pages tree.
235   - for (auto& kid: cur_pages.getKey("/Kids").aitems()) {
236   - if (kid.isDictionaryOfType("/Pages")) {
237   - pushInheritedAttributesToPageInternal(
238   - kid, key_ancestors, allow_changes, warn_skipped_keys);
239   - } else {
240   - // Add all available inheritable attributes not present in this object to this object.
241   - for (auto const& iter: key_ancestors) {
242   - std::string const& key = iter.first;
243   - if (!kid.hasKey(key)) {
244   - kid.replaceKey(key, iter.second.back());
245   - } else {
246   - QTC::TC("qpdf", "QPDF opt page resource hides ancestor");
247   - }
248   - }
249   - }
250   - }
251   -
252   - // For each inheritable key, pop the stack. If the stack becomes empty, remove it from the map.
253   - // That way, the invariant that the list of keys in key_ancestors is exactly those keys for
254   - // which inheritable attributes are available.
255   -
256   - if (!inheritable_keys.empty()) {
257   - for (auto const& key: inheritable_keys) {
258   - key_ancestors[key].pop_back();
259   - if (key_ancestors[key].empty()) {
260   - key_ancestors.erase(key);
261   - }
262   - }
263   - } else {
264   - QTC::TC("qpdf", "QPDF opt no inheritable keys");
265   - }
266   -}
267   -
268   -void
269   -Lin::updateObjectMaps(
270   - ObjUser const& first_ou,
271   - QPDFObjectHandle first_oh,
272   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
273   -{
274   - QPDFObjGen::set visited;
275   - std::vector<UpdateObjectMapsFrame> pending;
276   - pending.emplace_back(first_ou, first_oh, true);
277   - // Traverse the object tree from this point taking care to avoid crossing page boundaries.
278   - std::unique_ptr<ObjUser> thumb_ou;
279   - while (!pending.empty()) {
280   - auto cur = pending.back();
281   - pending.pop_back();
282   -
283   - bool is_page_node = false;
284   -
285   - if (cur.oh.isDictionaryOfType("/Page")) {
286   - is_page_node = true;
287   - if (!cur.top) {
288   - continue;
289   - }
290   - }
291   -
292   - if (cur.oh.isIndirect()) {
293   - QPDFObjGen og(cur.oh.getObjGen());
294   - if (!visited.add(og)) {
295   - QTC::TC("qpdf", "QPDF opt loop detected");
296   - continue;
297   - }
298   - m->obj_user_to_objects[cur.ou].insert(og);
299   - m->object_to_obj_users[og].insert(cur.ou);
300   - }
301   -
302   - if (cur.oh.isArray()) {
303   - for (auto const& item: cur.oh.as_array()) {
304   - pending.emplace_back(cur.ou, item, false);
305   - }
306   - } else if (cur.oh.isDictionary() || cur.oh.isStream()) {
307   - QPDFObjectHandle dict = cur.oh;
308   - bool is_stream = cur.oh.isStream();
309   - int ssp = 0;
310   - if (is_stream) {
311   - dict = cur.oh.getDict();
312   - if (skip_stream_parameters) {
313   - ssp = skip_stream_parameters(cur.oh);
314   - }
315   - }
316   -
317   - for (auto& [key, value]: dict.as_dictionary()) {
318   - if (value.null()) {
319   - continue;
320   - }
321   -
322   - if (is_page_node && (key == "/Thumb")) {
323   - // Traverse page thumbnail dictionaries as a special case. There can only ever
324   - // be one /Thumb key on a page, and we see at most one page node per call.
325   - thumb_ou = std::make_unique<ObjUser>(ObjUser::ou_thumb, cur.ou.pageno);
326   - pending.emplace_back(*thumb_ou, dict.getKey(key), false);
327   - } else if (is_page_node && (key == "/Parent")) {
328   - // Don't traverse back up the page tree
329   - } else if (
330   - ((ssp >= 1) && (key == "/Length")) ||
331   - ((ssp >= 2) && ((key == "/Filter") || (key == "/DecodeParms")))) {
332   - // Don't traverse into stream parameters that we are not going to write.
333   - } else {
334   - pending.emplace_back(cur.ou, value, false);
335   - }
336   - }
337   - }
338   - }
339   -}
340   -
341   -void
342   -Lin::filterCompressedObjects(std::map<int, int> const& object_stream_data)
343   -{
344   - if (object_stream_data.empty()) {
345   - return;
346   - }
347   -
348   - // Transform object_to_obj_users and obj_user_to_objects so that they refer only to uncompressed
349   - // objects. If something is a user of a compressed object, then it is really a user of the
350   - // object stream that contains it.
351   -
352   - std::map<ObjUser, std::set<QPDFObjGen>> t_obj_user_to_objects;
353   - std::map<QPDFObjGen, std::set<ObjUser>> t_object_to_obj_users;
354   -
355   - for (auto const& i1: m->obj_user_to_objects) {
356   - ObjUser const& ou = i1.first;
357   - // Loop over objects.
358   - for (auto const& og: i1.second) {
359   - auto i2 = object_stream_data.find(og.getObj());
360   - if (i2 == object_stream_data.end()) {
361   - t_obj_user_to_objects[ou].insert(og);
362   - } else {
363   - t_obj_user_to_objects[ou].insert(QPDFObjGen(i2->second, 0));
364   - }
365   - }
366   - }
367   -
368   - for (auto const& i1: m->object_to_obj_users) {
369   - QPDFObjGen const& og = i1.first;
370   - // Loop over obj_users.
371   - for (auto const& ou: i1.second) {
372   - auto i2 = object_stream_data.find(og.getObj());
373   - if (i2 == object_stream_data.end()) {
374   - t_object_to_obj_users[og].insert(ou);
375   - } else {
376   - t_object_to_obj_users[QPDFObjGen(i2->second, 0)].insert(ou);
377   - }
378   - }
379   - }
380   -
381   - m->obj_user_to_objects = t_obj_user_to_objects;
382   - m->object_to_obj_users = t_object_to_obj_users;
383   -}
384   -
385   -void
386   -Lin::filterCompressedObjects(QPDFWriter::ObjTable const& obj)
387   -{
388   - if (obj.getStreamsEmpty()) {
389   - return;
390   - }
391   -
392   - // Transform object_to_obj_users and obj_user_to_objects so that they refer only to uncompressed
393   - // objects. If something is a user of a compressed object, then it is really a user of the
394   - // object stream that contains it.
395   -
396   - std::map<ObjUser, std::set<QPDFObjGen>> t_obj_user_to_objects;
397   - std::map<QPDFObjGen, std::set<ObjUser>> t_object_to_obj_users;
398   -
399   - for (auto const& i1: m->obj_user_to_objects) {
400   - ObjUser const& ou = i1.first;
401   - // Loop over objects.
402   - for (auto const& og: i1.second) {
403   - if (obj.contains(og)) {
404   - if (auto const& i2 = obj[og].object_stream; i2 <= 0) {
405   - t_obj_user_to_objects[ou].insert(og);
406   - } else {
407   - t_obj_user_to_objects[ou].insert(QPDFObjGen(i2, 0));
408   - }
409   - }
410   - }
411   - }
412   -
413   - for (auto const& i1: m->object_to_obj_users) {
414   - QPDFObjGen const& og = i1.first;
415   - if (obj.contains(og)) {
416   - // Loop over obj_users.
417   - for (auto const& ou: i1.second) {
418   - if (auto i2 = obj[og].object_stream; i2 <= 0) {
419   - t_object_to_obj_users[og].insert(ou);
420   - } else {
421   - t_object_to_obj_users[QPDFObjGen(i2, 0)].insert(ou);
422   - }
423   - }
424   - }
425   - }
426   -
427   - m->obj_user_to_objects = t_obj_user_to_objects;
428   - m->object_to_obj_users = t_object_to_obj_users;
429   -}
libqpdf/QPDF_pages.cc
  1 +#include <qpdf/QPDFPageDocumentHelper.hh>
1 2 #include <qpdf/QPDF_private.hh>
2 3  
  4 +#include <qpdf/QPDFAcroFormDocumentHelper.hh>
3 5 #include <qpdf/QPDFExc.hh>
4 6 #include <qpdf/QPDFObjectHandle_private.hh>
5 7 #include <qpdf/QTC.hh>
6 8 #include <qpdf/QUtil.hh>
  9 +#include <qpdf/Util.hh>
7 10  
8 11 // In support of page manipulation APIs, these methods internally maintain state about pages in a
9 12 // pair of data structures: all_pages, which is a vector of page objects, and pageobj_to_pages_pos,
... ... @@ -42,10 +45,16 @@ using Pages = QPDF::Doc::Pages;
42 45 std::vector<QPDFObjectHandle> const&
43 46 QPDF::getAllPages()
44 47 {
  48 + return m->pages.all();
  49 +}
  50 +
  51 +std::vector<QPDFObjectHandle> const&
  52 +Pages::cache()
  53 +{
45 54 // Note that pushInheritedAttributesToPage may also be used to initialize m->all_pages.
46   - if (m->all_pages.empty() && !m->invalid_page_found) {
47   - m->ever_called_get_all_pages = true;
48   - auto root = getRoot();
  55 + if (all_pages.empty() && !invalid_page_found) {
  56 + ever_called_get_all_pages_ = true;
  57 + auto root = qpdf.getRoot();
49 58 QPDFObjGen::set visited;
50 59 QPDFObjGen::set seen;
51 60 QPDFObjectHandle pages = root.getKey("/Pages");
... ... @@ -77,18 +86,18 @@ QPDF::getAllPages()
77 86 qpdf_e_pages, m->file->getName(), "", 0, "root of pages tree has no /Kids array");
78 87 }
79 88 try {
80   - m->pages.getAllPagesInternal(pages, visited, seen, false, false);
  89 + getAllPagesInternal(pages, visited, seen, false, false);
81 90 } catch (...) {
82   - m->all_pages.clear();
83   - m->invalid_page_found = false;
  91 + all_pages.clear();
  92 + invalid_page_found = false;
84 93 throw;
85 94 }
86   - if (m->invalid_page_found) {
87   - m->pages.flattenPagesTree();
88   - m->invalid_page_found = false;
  95 + if (invalid_page_found) {
  96 + flattenPagesTree();
  97 + invalid_page_found = false;
89 98 }
90 99 }
91   - return m->all_pages;
  100 + return all_pages;
92 101 }
93 102  
94 103 void
... ... @@ -137,7 +146,7 @@ Pages::getAllPagesInternal(
137 146  
138 147 if (!kid.isDictionary()) {
139 148 kid.warn("Pages tree includes non-dictionary object; ignoring");
140   - m->invalid_page_found = true;
  149 + invalid_page_found = true;
141 150 continue;
142 151 }
143 152 if (!kid.isIndirect()) {
... ... @@ -206,7 +215,7 @@ Pages::getAllPagesInternal(
206 215 cur_node.warn(
207 216 "kid " + std::to_string(i) +
208 217 " (from 0) appears more than once in the pages tree; ignoring duplicate");
209   - m->invalid_page_found = true;
  218 + invalid_page_found = true;
210 219 kid = QPDFObjectHandle::newNull();
211 220 continue;
212 221 }
... ... @@ -223,11 +232,11 @@ Pages::getAllPagesInternal(
223 232 if (m->reconstructed_xref && errors > 2) {
224 233 cur_node.warn(
225 234 "kid " + std::to_string(i) + " (from 0) has too many errors; ignoring page");
226   - m->invalid_page_found = true;
  235 + invalid_page_found = true;
227 236 kid = QPDFObjectHandle::newNull();
228 237 continue;
229 238 }
230   - m->all_pages.emplace_back(kid);
  239 + all_pages.emplace_back(kid);
231 240 }
232 241 }
233 242 }
... ... @@ -235,13 +244,19 @@ Pages::getAllPagesInternal(
235 244 void
236 245 QPDF::updateAllPagesCache()
237 246 {
  247 + m->pages.update_cache();
  248 +}
  249 +
  250 +void
  251 +Pages::update_cache()
  252 +{
238 253 // Force regeneration of the pages cache. We force immediate recalculation of all_pages since
239 254 // users may have references to it that they got from calls to getAllPages(). We can defer
240 255 // recalculation of pageobj_to_pages_pos until needed.
241   - m->all_pages.clear();
242   - m->pageobj_to_pages_pos.clear();
243   - m->pushed_inherited_attributes_to_pages = false;
244   - getAllPages();
  256 + all_pages.clear();
  257 + pageobj_to_pages_pos.clear();
  258 + pushed_inherited_attributes_to_pages = false;
  259 + cache();
245 260 }
246 261  
247 262 void
... ... @@ -249,30 +264,30 @@ Pages::flattenPagesTree()
249 264 {
250 265 // If not already done, flatten the /Pages structure and initialize pageobj_to_pages_pos.
251 266  
252   - if (!m->pageobj_to_pages_pos.empty()) {
  267 + if (!pageobj_to_pages_pos.empty()) {
253 268 return;
254 269 }
255 270  
256   - // Push inherited objects down to the /Page level. As a side effect m->all_pages will also be
  271 + // Push inherited objects down to the /Page level. As a side effect all_pages will also be
257 272 // generated.
258 273 pushInheritedAttributesToPage(true, true);
259 274  
260 275 QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages");
261 276  
262   - size_t const len = m->all_pages.size();
  277 + size_t const len = all_pages.size();
263 278 for (size_t pos = 0; pos < len; ++pos) {
264 279 // Populate pageobj_to_pages_pos and fix parent pointer. There should be no duplicates at
265 280 // this point because pushInheritedAttributesToPage calls getAllPages which resolves
266 281 // duplicates.
267   - insertPageobjToPage(m->all_pages.at(pos), toI(pos), true);
268   - m->all_pages.at(pos).replaceKey("/Parent", pages);
  282 + insertPageobjToPage(all_pages.at(pos), toI(pos), true);
  283 + all_pages.at(pos).replaceKey("/Parent", pages);
269 284 }
270 285  
271   - pages.replaceKey("/Kids", QPDFObjectHandle::newArray(m->all_pages));
  286 + pages.replaceKey("/Kids", Array(all_pages));
272 287 // /Count has not changed
273 288 if (pages.getKey("/Count").getUIntValue() != len) {
274   - if (m->invalid_page_found && pages.getKey("/Count").getUIntValue() > len) {
275   - pages.replaceKey("/Count", QPDFObjectHandle::newInteger(toI(len)));
  289 + if (invalid_page_found && pages.getKey("/Count").getUIntValue() > len) {
  290 + pages.replaceKey("/Count", Integer(len));
276 291 } else {
277 292 throw std::runtime_error("/Count is wrong after flattening pages tree");
278 293 }
... ... @@ -280,11 +295,141 @@ Pages::flattenPagesTree()
280 295 }
281 296  
282 297 void
  298 +QPDF::pushInheritedAttributesToPage()
  299 +{
  300 + // Public API should not have access to allow_changes.
  301 + m->pages.pushInheritedAttributesToPage(true, false);
  302 +}
  303 +
  304 +void
  305 +Pages::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
  306 +{
  307 + // Traverse pages tree pushing all inherited resources down to the page level.
  308 +
  309 + // The record of whether we've done this is cleared by updateAllPagesCache(). If we're warning
  310 + // for skipped keys, re-traverse unconditionally.
  311 + if (pushed_inherited_attributes_to_pages && !warn_skipped_keys) {
  312 + return;
  313 + }
  314 +
  315 + // Calling cache() resolves any duplicated page objects, repairs broken nodes, and detects
  316 + // loops, so we don't have to do those activities here.
  317 + (void)cache();
  318 +
  319 + // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain
  320 + // values for them.
  321 + std::map<std::string, std::vector<QPDFObjectHandle>> key_ancestors;
  322 + pushInheritedAttributesToPageInternal(
  323 + m->trailer.getKey("/Root").getKey("/Pages"),
  324 + key_ancestors,
  325 + allow_changes,
  326 + warn_skipped_keys);
  327 + util::assertion(
  328 + key_ancestors.empty(),
  329 + "key_ancestors not empty after pushing inherited attributes to pages");
  330 + pushed_inherited_attributes_to_pages = true;
  331 + ever_pushed_inherited_attributes_to_pages_ = true;
  332 +}
  333 +
  334 +void
  335 +Pages::pushInheritedAttributesToPageInternal(
  336 + QPDFObjectHandle cur_pages,
  337 + std::map<std::string, std::vector<QPDFObjectHandle>>& key_ancestors,
  338 + bool allow_changes,
  339 + bool warn_skipped_keys)
  340 +{
  341 + // Make a list of inheritable keys. Only the keys /MediaBox, /CropBox, /Resources, and /Rotate
  342 + // are inheritable attributes. Push this object onto the stack of pages nodes that have values
  343 + // for this attribute.
  344 +
  345 + std::set<std::string> inheritable_keys;
  346 + for (auto const& key: cur_pages.getKeys()) {
  347 + if (key == "/MediaBox" || key == "/CropBox" || key == "/Resources" || key == "/Rotate") {
  348 + if (!allow_changes) {
  349 + throw QPDFExc(
  350 + qpdf_e_internal,
  351 + m->file->getName(),
  352 + m->last_object_description,
  353 + m->file->getLastOffset(),
  354 + "optimize detected an inheritable attribute when called in no-change mode");
  355 + }
  356 +
  357 + // This is an inheritable resource
  358 + inheritable_keys.insert(key);
  359 + QPDFObjectHandle oh = cur_pages.getKey(key);
  360 + QTC::TC("qpdf", "QPDF opt direct pages resource", oh.indirect() ? 0 : 1);
  361 + if (!oh.indirect()) {
  362 + if (!oh.isScalar()) {
  363 + // Replace shared direct object non-scalar resources with indirect objects to
  364 + // avoid copying large structures around.
  365 + cur_pages.replaceKey(key, qpdf.makeIndirectObject(oh));
  366 + oh = cur_pages.getKey(key);
  367 + } else {
  368 + // It's okay to copy scalars.
  369 + }
  370 + }
  371 + key_ancestors[key].push_back(oh);
  372 + if (key_ancestors[key].size() > 1) {
  373 + }
  374 + // Remove this resource from this node. It will be reattached at the page level.
  375 + cur_pages.removeKey(key);
  376 + } else if (!(key == "/Type" || key == "/Parent" || key == "/Kids" || key == "/Count")) {
  377 + // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not
  378 + // set), as we don't change these; but flattening removes intermediate /Pages nodes.
  379 + if (warn_skipped_keys && cur_pages.hasKey("/Parent")) {
  380 + warn(
  381 + qpdf_e_pages,
  382 + "Pages object: object " + cur_pages.id_gen().unparse(' '),
  383 + 0,
  384 + ("Unknown key " + key +
  385 + " in /Pages object is being discarded as a result of flattening the /Pages "
  386 + "tree"));
  387 + }
  388 + }
  389 + }
  390 +
  391 + // Process descendant nodes. This method does not perform loop detection because all code paths
  392 + // that lead here follow a call to getAllPages, which already throws an exception in the event
  393 + // of a loop in the pages tree.
  394 + for (auto& kid: cur_pages.getKey("/Kids").aitems()) {
  395 + if (kid.isDictionaryOfType("/Pages")) {
  396 + pushInheritedAttributesToPageInternal(
  397 + kid, key_ancestors, allow_changes, warn_skipped_keys);
  398 + } else {
  399 + // Add all available inheritable attributes not present in this object to this object.
  400 + for (auto const& iter: key_ancestors) {
  401 + std::string const& key = iter.first;
  402 + if (!kid.hasKey(key)) {
  403 + kid.replaceKey(key, iter.second.back());
  404 + } else {
  405 + QTC::TC("qpdf", "QPDF opt page resource hides ancestor");
  406 + }
  407 + }
  408 + }
  409 + }
  410 +
  411 + // For each inheritable key, pop the stack. If the stack becomes empty, remove it from the map.
  412 + // That way, the invariant that the list of keys in key_ancestors is exactly those keys for
  413 + // which inheritable attributes are available.
  414 +
  415 + if (!inheritable_keys.empty()) {
  416 + for (auto const& key: inheritable_keys) {
  417 + key_ancestors[key].pop_back();
  418 + if (key_ancestors[key].empty()) {
  419 + key_ancestors.erase(key);
  420 + }
  421 + }
  422 + } else {
  423 + QTC::TC("qpdf", "QPDF opt no inheritable keys");
  424 + }
  425 +}
  426 +
  427 +void
283 428 Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate)
284 429 {
285 430 QPDFObjGen og(obj.getObjGen());
286 431 if (check_duplicate) {
287   - if (!m->pageobj_to_pages_pos.insert(std::make_pair(og, pos)).second) {
  432 + if (!pageobj_to_pages_pos.insert(std::make_pair(og, pos)).second) {
288 433 // The library never calls insertPageobjToPage in a way that causes this to happen.
289 434 throw QPDFExc(
290 435 qpdf_e_pages,
... ... @@ -294,52 +439,51 @@ Pages::insertPageobjToPage(QPDFObjectHandle const&amp; obj, int pos, bool check_dupl
294 439 "duplicate page reference found; this would cause loss of data");
295 440 }
296 441 } else {
297   - m->pageobj_to_pages_pos[og] = pos;
  442 + pageobj_to_pages_pos[og] = pos;
298 443 }
299 444 }
300 445  
301 446 void
302   -Pages::insertPage(QPDFObjectHandle newpage, int pos)
  447 +Pages::insert(QPDFObjectHandle newpage, int pos)
303 448 {
304 449 // pos is numbered from 0, so pos = 0 inserts at the beginning and pos = npages adds to the end.
305 450  
306 451 flattenPagesTree();
307 452  
308   - if (!newpage.isIndirect()) {
  453 + if (!newpage.indirect()) {
309 454 newpage = qpdf.makeIndirectObject(newpage);
310   - } else if (newpage.getOwningQPDF() != &qpdf) {
311   - newpage.getQPDF().pushInheritedAttributesToPage();
  455 + } else if (newpage.qpdf() != &qpdf) {
  456 + newpage.qpdf()->pushInheritedAttributesToPage();
312 457 newpage = qpdf.copyForeignObject(newpage);
313 458 } else {
314 459 QTC::TC("qpdf", "QPDF insert indirect page");
315 460 }
316 461  
317   - if (pos < 0 || toS(pos) > m->all_pages.size()) {
  462 + if (pos < 0 || std::cmp_greater(pos, all_pages.size())) {
318 463 throw std::runtime_error("QPDF::insertPage called with pos out of range");
319 464 }
320 465  
321 466 QTC::TC(
322 467 "qpdf",
323 468 "QPDF insert page",
324   - (pos == 0) ? 0 : // insert at beginning
325   - (pos == toI(m->all_pages.size())) ? 1 // at end
326   - : 2); // insert in middle
  469 + pos == 0 ? 0 : // insert at beginning
  470 + std::cmp_equal(pos, size()) ? 1 // at end
  471 + : 2); // insert in middle
327 472  
328   - auto og = newpage.getObjGen();
329   - if (m->pageobj_to_pages_pos.contains(og)) {
330   - newpage = qpdf.makeIndirectObject(QPDFObjectHandle(newpage).shallowCopy());
  473 + if (pageobj_to_pages_pos.contains(newpage)) {
  474 + newpage = qpdf.makeIndirectObject(newpage.copy());
331 475 }
332 476  
333   - QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages");
334   - QPDFObjectHandle kids = pages.getKey("/Kids");
  477 + auto pages = qpdf.getRoot()["/Pages"];
  478 + Array kids = pages["/Kids"];
335 479  
336 480 newpage.replaceKey("/Parent", pages);
337   - kids.insertItem(pos, newpage);
338   - int npages = static_cast<int>(kids.size());
339   - pages.replaceKey("/Count", QPDFObjectHandle::newInteger(npages));
340   - m->all_pages.insert(m->all_pages.begin() + pos, newpage);
341   - for (int i = pos + 1; i < npages; ++i) {
342   - insertPageobjToPage(m->all_pages.at(toS(i)), i, false);
  481 + kids.insert(pos, newpage);
  482 + size_t npages = kids.size();
  483 + pages.replaceKey("/Count", Integer(npages));
  484 + all_pages.insert(all_pages.begin() + pos, newpage);
  485 + for (size_t i = static_cast<size_t>(pos) + 1; i < npages; ++i) {
  486 + insertPageobjToPage(all_pages.at(i), static_cast<int>(i), false);
343 487 }
344 488 insertPageobjToPage(newpage, pos, true);
345 489 }
... ... @@ -347,24 +491,30 @@ Pages::insertPage(QPDFObjectHandle newpage, int pos)
347 491 void
348 492 QPDF::removePage(QPDFObjectHandle page)
349 493 {
350   - int pos = findPage(page); // also ensures flat /Pages
  494 + m->pages.erase(page);
  495 +}
  496 +
  497 +void
  498 +Pages::erase(QPDFObjectHandle& page)
  499 +{
  500 + int pos = qpdf.findPage(page); // also ensures flat /Pages
351 501 QTC::TC(
352 502 "qpdf",
353 503 "QPDF remove page",
354   - (pos == 0) ? 0 : // remove at beginning
355   - (pos == toI(m->all_pages.size() - 1)) ? 1 // end
356   - : 2); // remove in middle
  504 + (pos == 0) ? 0 : // remove at beginning
  505 + (pos == toI(all_pages.size() - 1)) ? 1 // end
  506 + : 2); // remove in middle
357 507  
358   - QPDFObjectHandle pages = getRoot().getKey("/Pages");
  508 + QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages");
359 509 QPDFObjectHandle kids = pages.getKey("/Kids");
360 510  
361 511 kids.eraseItem(pos);
362 512 int npages = static_cast<int>(kids.size());
363 513 pages.replaceKey("/Count", QPDFObjectHandle::newInteger(npages));
364   - m->all_pages.erase(m->all_pages.begin() + pos);
365   - m->pageobj_to_pages_pos.erase(page.getObjGen());
  514 + all_pages.erase(all_pages.begin() + pos);
  515 + pageobj_to_pages_pos.erase(page.getObjGen());
366 516 for (int i = pos; i < npages; ++i) {
367   - m->pages.insertPageobjToPage(m->all_pages.at(toS(i)), i, false);
  517 + m->pages.insertPageobjToPage(all_pages.at(toS(i)), i, false);
368 518 }
369 519 }
370 520  
... ... @@ -375,17 +525,16 @@ QPDF::addPageAt(QPDFObjectHandle newpage, bool before, QPDFObjectHandle refpage)
375 525 if (!before) {
376 526 ++refpos;
377 527 }
378   - m->pages.insertPage(newpage, refpos);
  528 + m->pages.insert(newpage, refpos);
379 529 }
380 530  
381 531 void
382 532 QPDF::addPage(QPDFObjectHandle newpage, bool first)
383 533 {
384 534 if (first) {
385   - m->pages.insertPage(newpage, 0);
  535 + m->pages.insert(newpage, 0);
386 536 } else {
387   - m->pages.insertPage(
388   - newpage, getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt());
  537 + m->pages.insert(newpage, getRoot()["/Pages"]["/Count"].getIntValueAsInt());
389 538 }
390 539 }
391 540  
... ... @@ -398,9 +547,15 @@ QPDF::findPage(QPDFObjectHandle&amp; page)
398 547 int
399 548 QPDF::findPage(QPDFObjGen og)
400 549 {
401   - m->pages.flattenPagesTree();
402   - auto it = m->pageobj_to_pages_pos.find(og);
403   - if (it == m->pageobj_to_pages_pos.end()) {
  550 + return m->pages.find(og);
  551 +}
  552 +
  553 +int
  554 +Pages::find(QPDFObjGen og)
  555 +{
  556 + flattenPagesTree();
  557 + auto it = pageobj_to_pages_pos.find(og);
  558 + if (it == pageobj_to_pages_pos.end()) {
404 559 throw QPDFExc(
405 560 qpdf_e_pages,
406 561 m->file->getName(),
... ... @@ -410,3 +565,168 @@ QPDF::findPage(QPDFObjGen og)
410 565 }
411 566 return (*it).second;
412 567 }
  568 +
  569 +class QPDFPageDocumentHelper::Members
  570 +{
  571 +};
  572 +
  573 +QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) :
  574 + QPDFDocumentHelper(qpdf)
  575 +{
  576 +}
  577 +
  578 +QPDFPageDocumentHelper&
  579 +QPDFPageDocumentHelper::get(QPDF& qpdf)
  580 +{
  581 + return qpdf.doc().page_dh();
  582 +}
  583 +
  584 +void
  585 +QPDFPageDocumentHelper::validate(bool repair)
  586 +{
  587 +}
  588 +
  589 +std::vector<QPDFPageObjectHelper>
  590 +QPDFPageDocumentHelper::getAllPages()
  591 +{
  592 + auto& pp = qpdf.doc().pages();
  593 + return {pp.begin(), pp.end()};
  594 +}
  595 +
  596 +void
  597 +QPDFPageDocumentHelper::pushInheritedAttributesToPage()
  598 +{
  599 + qpdf.pushInheritedAttributesToPage();
  600 +}
  601 +
  602 +void
  603 +QPDFPageDocumentHelper::removeUnreferencedResources()
  604 +{
  605 + for (auto& ph: getAllPages()) {
  606 + ph.removeUnreferencedResources();
  607 + }
  608 +}
  609 +
  610 +void
  611 +QPDFPageDocumentHelper::addPage(QPDFPageObjectHelper newpage, bool first)
  612 +{
  613 + qpdf.doc().pages().insert(newpage, first ? 0 : qpdf.doc().pages().size());
  614 +}
  615 +
  616 +void
  617 +QPDFPageDocumentHelper::addPageAt(
  618 + QPDFPageObjectHelper newpage, bool before, QPDFPageObjectHelper refpage)
  619 +{
  620 + qpdf.addPageAt(newpage.getObjectHandle(), before, refpage.getObjectHandle());
  621 +}
  622 +
  623 +void
  624 +QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page)
  625 +{
  626 + qpdf.removePage(page.getObjectHandle());
  627 +}
  628 +
  629 +void
  630 +QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags)
  631 +{
  632 + qpdf.doc().pages().flatten_annotations(required_flags, forbidden_flags);
  633 +}
  634 +
  635 +void
  636 +Pages::flatten_annotations(int required_flags, int forbidden_flags)
  637 +{
  638 + auto& afdh = qpdf.doc().acroform();
  639 + if (afdh.getNeedAppearances()) {
  640 + qpdf.getRoot()
  641 + .getKey("/AcroForm")
  642 + .warn(
  643 + "document does not have updated appearance streams, so form fields "
  644 + "will not be flattened");
  645 + }
  646 + for (QPDFPageObjectHelper ph: all()) {
  647 + QPDFObjectHandle resources = ph.getAttribute("/Resources", true);
  648 + if (!resources.isDictionary()) {
  649 + // As of #1521, this should be impossible unless a user inserted an invalid page.
  650 + resources = ph.getObjectHandle().replaceKeyAndGetNew("/Resources", Dictionary::empty());
  651 + }
  652 + flatten_annotations_for_page(ph, resources, afdh, required_flags, forbidden_flags);
  653 + }
  654 + if (!afdh.getNeedAppearances()) {
  655 + qpdf.getRoot().removeKey("/AcroForm");
  656 + }
  657 +}
  658 +
  659 +void
  660 +Pages::flatten_annotations_for_page(
  661 + QPDFPageObjectHelper& page,
  662 + QPDFObjectHandle& resources,
  663 + QPDFAcroFormDocumentHelper& afdh,
  664 + int required_flags,
  665 + int forbidden_flags)
  666 +{
  667 + bool need_appearances = afdh.getNeedAppearances();
  668 + std::vector<QPDFAnnotationObjectHelper> annots = page.getAnnotations();
  669 + std::vector<QPDFObjectHandle> new_annots;
  670 + std::string new_content;
  671 + int rotate = 0;
  672 + QPDFObjectHandle rotate_obj = page.getObjectHandle().getKey("/Rotate");
  673 + if (rotate_obj.isInteger() && rotate_obj.getIntValue()) {
  674 + rotate = rotate_obj.getIntValueAsInt();
  675 + }
  676 + int next_fx = 1;
  677 + for (auto& aoh: annots) {
  678 + QPDFObjectHandle as = aoh.getAppearanceStream("/N");
  679 + bool is_widget = (aoh.getSubtype() == "/Widget");
  680 + bool process = true;
  681 + if (need_appearances && is_widget) {
  682 + process = false;
  683 + }
  684 + if (process && as.isStream()) {
  685 + if (is_widget) {
  686 + QPDFFormFieldObjectHelper ff = afdh.getFieldForAnnotation(aoh);
  687 + QPDFObjectHandle as_resources = as.getDict().getKey("/Resources");
  688 + if (as_resources.isIndirect()) {
  689 + ;
  690 + as.getDict().replaceKey("/Resources", as_resources.shallowCopy());
  691 + as_resources = as.getDict().getKey("/Resources");
  692 + }
  693 + as_resources.mergeResources(ff.getDefaultResources());
  694 + } else {
  695 + QTC::TC("qpdf", "QPDFPageDocumentHelper non-widget annotation");
  696 + }
  697 + std::string name = resources.getUniqueResourceName("/Fxo", next_fx);
  698 + std::string content =
  699 + aoh.getPageContentForAppearance(name, rotate, required_flags, forbidden_flags);
  700 + if (!content.empty()) {
  701 + resources.mergeResources("<< /XObject << >> >>"_qpdf);
  702 + resources.getKey("/XObject").replaceKey(name, as);
  703 + ++next_fx;
  704 + }
  705 + new_content += content;
  706 + } else if (process && !aoh.getAppearanceDictionary().null()) {
  707 + // If an annotation has no selected appearance stream, just drop the annotation when
  708 + // flattening. This can happen for unchecked checkboxes and radio buttons, popup windows
  709 + // associated with comments that aren't visible, and other types of annotations that
  710 + // aren't visible. Annotations that have no appearance streams at all, such as Link,
  711 + // Popup, and Projection, should be preserved.
  712 + } else {
  713 + new_annots.push_back(aoh.getObjectHandle());
  714 + }
  715 + }
  716 + if (new_annots.size() != annots.size()) {
  717 + QPDFObjectHandle page_oh = page.getObjectHandle();
  718 + if (new_annots.empty()) {
  719 + page_oh.removeKey("/Annots");
  720 + } else {
  721 + QPDFObjectHandle old_annots = page_oh.getKey("/Annots");
  722 + QPDFObjectHandle new_annots_oh = QPDFObjectHandle::newArray(new_annots);
  723 + if (old_annots.isIndirect()) {
  724 + qpdf.replaceObject(old_annots.getObjGen(), new_annots_oh);
  725 + } else {
  726 + page_oh.replaceKey("/Annots", new_annots_oh);
  727 + }
  728 + }
  729 + page.addPageContents(qpdf.newStream("q\n"), true);
  730 + page.addPageContents(qpdf.newStream("\nQ\n" + new_content), false);
  731 + }
  732 +}
... ...
libqpdf/qpdf-c.cc
1 1 #include <qpdf/qpdf-c.h>
2 2  
3   -#include <qpdf/QPDF.hh>
  3 +#include <qpdf/QPDF_private.hh>
4 4  
5 5 #include <qpdf/BufferInputSource.hh>
6 6 #include <qpdf/Pl_Buffer.hh>
... ... @@ -1804,10 +1804,9 @@ qpdf_oh_replace_stream_data(
1804 1804 int
1805 1805 qpdf_get_num_pages(qpdf_data qpdf)
1806 1806 {
1807   - QTC::TC("qpdf", "qpdf-c called qpdf_num_pages");
1808 1807 int n = -1;
1809 1808 QPDF_ERROR_CODE code =
1810   - trap_errors(qpdf, [&n](qpdf_data q) { n = QIntC::to_int(q->qpdf->getAllPages().size()); });
  1809 + trap_errors(qpdf, [&n](qpdf_data q) { n = QIntC::to_int(q->qpdf->doc().pages().size()); });
1811 1810 if (code & QPDF_ERRORS) {
1812 1811 return -1;
1813 1812 }
... ... @@ -1817,10 +1816,10 @@ qpdf_get_num_pages(qpdf_data qpdf)
1817 1816 qpdf_oh
1818 1817 qpdf_get_page_n(qpdf_data qpdf, size_t i)
1819 1818 {
1820   - QTC::TC("qpdf", "qpdf-c called qpdf_get_page_n");
1821 1819 qpdf_oh result = 0;
1822   - QPDF_ERROR_CODE code = trap_errors(
1823   - qpdf, [&result, i](qpdf_data q) { result = new_object(q, q->qpdf->getAllPages().at(i)); });
  1820 + QPDF_ERROR_CODE code = trap_errors(qpdf, [&result, i](qpdf_data q) {
  1821 + result = new_object(q, q->qpdf->doc().pages().all().at(i));
  1822 + });
1824 1823 if ((code & QPDF_ERRORS) || (result == 0)) {
1825 1824 return qpdf_oh_new_uninitialized(qpdf);
1826 1825 }
... ...
libqpdf/qpdf/QPDFObjectHandle_private.hh
... ... @@ -305,10 +305,10 @@ namespace qpdf
305 305 explicit Integer(std::integral auto value) :
306 306 Integer(static_cast<long long>(value))
307 307 {
308   - if constexpr (
309   - std::numeric_limits<decltype(value)>::max() >
310   - std::numeric_limits<long long>::max()) {
311   - if (value > std::numeric_limits<long long>::max()) {
  308 + if constexpr (std::cmp_greater(
  309 + std::numeric_limits<decltype(value)>::max(),
  310 + std::numeric_limits<long long>::max())) {
  311 + if (std::cmp_greater(value, std::numeric_limits<long long>::max())) {
312 312 throw std::overflow_error("overflow constructing Integer");
313 313 }
314 314 }
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -295,491 +295,65 @@ class QPDF::PatternFinder final: public InputSource::Finder
295 295 class QPDF::Doc
296 296 {
297 297 public:
  298 + class Encryption;
298 299 class JobSetter;
  300 + class Linearization;
  301 + class Objects;
  302 + class Pages;
299 303 class ParseGuard;
300 304 class Resolver;
301 305 class Writer;
302 306  
303   - class Encryption
  307 + // This is the common base-class for all document components. It is used by the other document
  308 + // components to access common functionality. It is not meant to be used directly by the user.
  309 + class Common
304 310 {
305 311 public:
306   - // This class holds data read from the encryption dictionary.
307   - Encryption(
308   - int V,
309   - int R,
310   - int Length_bytes,
311   - int P,
312   - std::string const& O,
313   - std::string const& U,
314   - std::string const& OE,
315   - std::string const& UE,
316   - std::string const& Perms,
317   - std::string const& id1,
318   - bool encrypt_metadata) :
319   - V(V),
320   - R(R),
321   - Length_bytes(Length_bytes),
322   - P(static_cast<unsigned long long>(P)),
323   - O(O),
324   - U(U),
325   - OE(OE),
326   - UE(UE),
327   - Perms(Perms),
328   - id1(id1),
329   - encrypt_metadata(encrypt_metadata)
330   - {
331   - }
332   - Encryption(int V, int R, int Length_bytes, bool encrypt_metadata) :
333   - V(V),
334   - R(R),
335   - Length_bytes(Length_bytes),
336   - encrypt_metadata(encrypt_metadata)
337   - {
338   - }
339   -
340   - int getV() const;
341   - int getR() const;
342   - int getLengthBytes() const;
343   - int getP() const;
344   - // Bits in P are numbered from 1 as in the PDF spec.
345   - bool getP(size_t bit) const;
346   - std::string const& getO() const;
347   - std::string const& getU() const;
348   - std::string const& getOE() const;
349   - std::string const& getUE() const;
350   - std::string const& getPerms() const;
351   - std::string const& getId1() const;
352   - bool getEncryptMetadata() const;
353   - // Bits in P are numbered from 1 as in the PDF spec.
354   - void setP(size_t bit, bool val);
355   - void setP(unsigned long val);
356   - void setO(std::string const&);
357   - void setU(std::string const&);
358   - void setId1(std::string const& val);
359   - void setV5EncryptionParameters(
360   - std::string const& O,
361   - std::string const& OE,
362   - std::string const& U,
363   - std::string const& UE,
364   - std::string const& Perms);
365   -
366   - std::string compute_encryption_key(std::string const& password) const;
367   -
368   - bool
369   - check_owner_password(std::string& user_password, std::string const& owner_password) const;
370   -
371   - bool check_user_password(std::string const& user_password) const;
372   -
373   - std::string
374   - recover_encryption_key_with_password(std::string const& password, bool& perms_valid) const;
375   -
376   - void compute_encryption_O_U(char const* user_password, char const* owner_password);
377   -
378   - std::string
379   - compute_encryption_parameters_V5(char const* user_password, char const* owner_password);
380   -
381   - std::string compute_parameters(char const* user_password, char const* owner_password);
  312 + Common() = delete;
  313 + Common(Common const&) = default;
  314 + Common(Common&&) = delete;
  315 + Common& operator=(Common const&) = delete;
  316 + Common& operator=(Common&&) = delete;
  317 + ~Common() = default;
  318 +
  319 + inline Common(QPDF& qpdf, QPDF::Members* m);
  320 +
  321 + void stopOnError(std::string const& message);
  322 + void warn(QPDFExc const& e);
  323 + void warn(
  324 + qpdf_error_code_e error_code,
  325 + std::string const& object,
  326 + qpdf_offset_t offset,
  327 + std::string const& message);
382 328  
383   - private:
384   - static constexpr unsigned int OU_key_bytes_V4 = 16; // ( == sizeof(MD5::Digest)
385   -
386   - Encryption(Encryption const&) = delete;
387   - Encryption& operator=(Encryption const&) = delete;
388   -
389   - std::string hash_V5(
390   - std::string const& password, std::string const& salt, std::string const& udata) const;
391   - std::string
392   - compute_O_value(std::string const& user_password, std::string const& owner_password) const;
393   - std::string compute_U_value(std::string const& user_password) const;
394   - std::string compute_encryption_key_from_password(std::string const& password) const;
395   - std::string recover_encryption_key_with_password(std::string const& password) const;
396   - bool check_owner_password_V4(
397   - std::string& user_password, std::string const& owner_password) const;
398   - bool check_owner_password_V5(std::string const& owner_passworda) const;
399   - std::string compute_Perms_value_V5_clear() const;
400   - std::string compute_O_rc4_key(
401   - std::string const& user_password, std::string const& owner_password) const;
402   - std::string compute_U_value_R2(std::string const& user_password) const;
403   - std::string compute_U_value_R3(std::string const& user_password) const;
404   - bool check_user_password_V4(std::string const& user_password) const;
405   - bool check_user_password_V5(std::string const& user_password) const;
406   -
407   - int V;
408   - int R;
409   - int Length_bytes;
410   - std::bitset<32> P{0xfffffffc}; // Specification always requires bits 1 and 2 to be cleared.
411   - std::string O;
412   - std::string U;
413   - std::string OE;
414   - std::string UE;
415   - std::string Perms;
416   - std::string id1;
417   - bool encrypt_metadata;
418   - }; // class QPDF::Doc::Encryption
419   -
420   - class Linearization
421   - {
422   - public:
423   - Linearization() = delete;
424   - Linearization(Linearization const&) = delete;
425   - Linearization(Linearization&&) = delete;
426   - Linearization& operator=(Linearization const&) = delete;
427   - Linearization& operator=(Linearization&&) = delete;
428   - ~Linearization() = default;
429   -
430   - Linearization(QPDF& qpdf, QPDF::Members* m) :
431   - qpdf(qpdf),
432   - m(m)
433   - {
434   - }
  329 + static QPDFExc damagedPDF(
  330 + InputSource& input,
  331 + std::string const& object,
  332 + qpdf_offset_t offset,
  333 + std::string const& message);
  334 + QPDFExc
  335 + damagedPDF(InputSource& input, qpdf_offset_t offset, std::string const& message) const;
  336 + QPDFExc damagedPDF(
  337 + std::string const& object, qpdf_offset_t offset, std::string const& message) const;
  338 + QPDFExc damagedPDF(std::string const& object, std::string const& message) const;
  339 + QPDFExc damagedPDF(qpdf_offset_t offset, std::string const& message) const;
  340 + QPDFExc damagedPDF(std::string const& message) const;
435 341  
436   - // For QPDFWriter:
437   -
438   - template <typename T>
439   - void optimize_internal(
440   - T const& object_stream_data,
441   - bool allow_changes = true,
442   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters = nullptr);
443   - void optimize(
444   - QPDFWriter::ObjTable const& obj,
445   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters);
446   -
447   - // Get lists of all objects in order according to the part of a linearized file that they
448   - // belong to.
449   - void getLinearizedParts(
450   - QPDFWriter::ObjTable const& obj,
451   - std::vector<QPDFObjectHandle>& part4,
452   - std::vector<QPDFObjectHandle>& part6,
453   - std::vector<QPDFObjectHandle>& part7,
454   - std::vector<QPDFObjectHandle>& part8,
455   - std::vector<QPDFObjectHandle>& part9);
456   -
457   - void generateHintStream(
458   - QPDFWriter::NewObjTable const& new_obj,
459   - QPDFWriter::ObjTable const& obj,
460   - std::string& hint_stream,
461   - int& S,
462   - int& O,
463   - bool compressed);
464   -
465   - // methods to support linearization checking -- implemented in QPDF_linearization.cc
466   -
467   - void readLinearizationData();
468   - void checkLinearizationInternal();
469   - void dumpLinearizationDataInternal();
470   - void linearizationWarning(std::string_view);
471   - qpdf::Dictionary readHintStream(Pipeline&, qpdf_offset_t offset, size_t length);
472   - void readHPageOffset(BitStream);
473   - void readHSharedObject(BitStream);
474   - void readHGeneric(BitStream, HGeneric&);
475   - qpdf_offset_t maxEnd(ObjUser const& ou);
476   - qpdf_offset_t getLinearizationOffset(QPDFObjGen);
477   - QPDFObjectHandle
478   - getUncompressedObject(QPDFObjectHandle&, std::map<int, int> const& object_stream_data);
479   - QPDFObjectHandle getUncompressedObject(QPDFObjectHandle&, QPDFWriter::ObjTable const& obj);
480   - int lengthNextN(int first_object, int n);
481   - void checkHPageOffset(
482   - std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj);
483   - void checkHSharedObject(
484   - std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj);
485   - void checkHOutlines();
486   - void dumpHPageOffset();
487   - void dumpHSharedObject();
488   - void dumpHGeneric(HGeneric&);
489   - qpdf_offset_t adjusted_offset(qpdf_offset_t offset);
490   - template <typename T>
491   - void calculateLinearizationData(T const& object_stream_data);
492   - template <typename T>
493   - void pushOutlinesToPart(
494   - std::vector<QPDFObjectHandle>& part,
495   - std::set<QPDFObjGen>& lc_outlines,
496   - T const& object_stream_data);
497   - int outputLengthNextN(
498   - int in_object,
499   - int n,
500   - QPDFWriter::NewObjTable const& new_obj,
501   - QPDFWriter::ObjTable const& obj);
502   - void calculateHPageOffset(
503   - QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
504   - void calculateHSharedObject(
505   - QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
506 342 void
507   - calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
508   - void writeHPageOffset(BitWriter&);
509   - void writeHSharedObject(BitWriter&);
510   - void writeHGeneric(BitWriter&, HGeneric&);
511   -
512   - // Methods to support optimization
513   -
514   - void updateObjectMaps(
515   - ObjUser const& ou,
516   - QPDFObjectHandle oh,
517   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters);
518   - void filterCompressedObjects(std::map<int, int> const& object_stream_data);
519   - void filterCompressedObjects(QPDFWriter::ObjTable const& object_stream_data);
520   -
521   - private:
522   - QPDF& qpdf;
523   - QPDF::Members* m;
524   - };
525   -
526   - class Objects
527   - {
528   - public:
529   - class Foreign
  343 + no_ci_stop_if(
  344 + bool condition, std::string const& message, std::string const& context = {}) const
530 345 {
531   - class Copier
532   - {
533   - public:
534   - Copier(QPDF& qpdf) :
535   - qpdf(qpdf)
536   - {
537   - }
538   -
539   - QPDFObjectHandle copied(QPDFObjectHandle const& foreign);
540   -
541   - private:
542   - QPDFObjectHandle
543   - replace_indirect_object(QPDFObjectHandle const& foreign, bool top = false);
544   - void reserve_objects(QPDFObjectHandle const& foreign, bool top = false);
545   -
546   - QPDF& qpdf;
547   - std::map<QPDFObjGen, QPDFObjectHandle> object_map;
548   - std::vector<QPDFObjectHandle> to_copy;
549   - QPDFObjGen::set visiting;
550   - };
551   -
552   - public:
553   - Foreign(QPDF& qpdf) :
554   - qpdf(qpdf)
555   - {
556   - }
557   -
558   - Foreign() = delete;
559   - Foreign(Foreign const&) = delete;
560   - Foreign(Foreign&&) = delete;
561   - Foreign& operator=(Foreign const&) = delete;
562   - Foreign& operator=(Foreign&&) = delete;
563   - ~Foreign() = default;
564   -
565   - // Return a local handle to the foreign object. Copy the foreign object if necessary.
566   - QPDFObjectHandle
567   - copied(QPDFObjectHandle const& foreign)
568   - {
569   - return copier(foreign).copied(foreign);
  346 + if (condition) {
  347 + throw damagedPDF(context, message);
570 348 }
571   -
572   - private:
573   - Copier& copier(QPDFObjectHandle const& foreign);
574   -
575   - QPDF& qpdf;
576   - std::map<unsigned long long, Copier> copiers;
577   - }; // class QPDF::Doc::Objects::Foreign
578   -
579   - class Streams
580   - {
581   - // Copier manages the copying of streams into this PDF. It is used both for copying
582   - // local and foreign streams.
583   - class Copier;
584   -
585   - public:
586   - Streams(QPDF& qpdf);
587   -
588   - Streams() = delete;
589   - Streams(Streams const&) = delete;
590   - Streams(Streams&&) = delete;
591   - Streams& operator=(Streams const&) = delete;
592   - Streams& operator=(Streams&&) = delete;
593   - ~Streams() = default;
594   -
595   - public:
596   - static bool
597   - pipeStreamData(
598   - QPDF* qpdf,
599   - QPDFObjGen og,
600   - qpdf_offset_t offset,
601   - size_t length,
602   - QPDFObjectHandle dict,
603   - bool is_root_metadata,
604   - Pipeline* pipeline,
605   - bool suppress_warnings,
606   - bool will_retry)
607   - {
608   - return qpdf->pipeStreamData(
609   - og,
610   - offset,
611   - length,
612   - dict,
613   - is_root_metadata,
614   - pipeline,
615   - suppress_warnings,
616   - will_retry);
617   - }
618   -
619   - QPDF&
620   - qpdf() const
621   - {
622   - return qpdf_;
623   - }
624   -
625   - std::shared_ptr<Copier>&
626   - copier()
627   - {
628   - return copier_;
629   - }
630   -
631   - bool immediate_copy_from() const;
632   -
633   - private:
634   - QPDF& qpdf_;
635   -
636   - std::shared_ptr<Copier> copier_;
637   - }; // class QPDF::Doc::Objects::Streams
638   -
639   - public:
640   - Objects() = delete;
641   - Objects(Objects const&) = delete;
642   - Objects(Objects&&) = delete;
643   - Objects& operator=(Objects const&) = delete;
644   - Objects& operator=(Objects&&) = delete;
645   - ~Objects() = default;
646   -
647   - Objects(QPDF& qpdf, QPDF::Members* m) :
648   - qpdf(qpdf),
649   - m(m),
650   - foreign_(qpdf),
651   - streams_(qpdf)
652   - {
653   - }
654   -
655   - Foreign&
656   - foreign()
657   - {
658   - return foreign_;
659   - }
660   -
661   - Streams&
662   - streams()
663   - {
664   - return streams_;
665 349 }
666 350  
667   - void parse(char const* password);
668   - std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
669   - void inParse(bool);
670   - QPDFObjGen nextObjGen();
671   - QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr<QPDFObject> const&);
672   - void updateCache(
673   - QPDFObjGen og,
674   - std::shared_ptr<QPDFObject> const& object,
675   - qpdf_offset_t end_before_space,
676   - qpdf_offset_t end_after_space,
677   - bool destroy = true);
678   - bool resolveXRefTable();
679   - QPDFObjectHandle readObjectAtOffset(
680   - qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref);
681   - QPDFTokenizer::Token readToken(InputSource& input, size_t max_len = 0);
682   - QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj);
683   - std::shared_ptr<QPDFObject> getObjectForParser(int id, int gen, bool parse_pdf);
684   - std::shared_ptr<QPDFObject> getObjectForJSON(int id, int gen);
685   - size_t tableSize();
686   -
687   - // For QPDFWriter:
688   -
689   - std::map<QPDFObjGen, QPDFXRefEntry> const& getXRefTableInternal();
690   - // Get a list of objects that would be permitted in an object stream.
691   - template <typename T>
692   - std::vector<T> getCompressibleObjGens();
693   - std::vector<QPDFObjGen> getCompressibleObjVector();
694   - std::vector<bool> getCompressibleObjSet();
695   -
696   - private:
697   - void setTrailer(QPDFObjectHandle obj);
698   - void reconstruct_xref(QPDFExc& e, bool found_startxref = true);
699   - void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false);
700   - bool parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes);
701   - bool read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
702   - bool read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
703   - qpdf_offset_t read_xrefTable(qpdf_offset_t offset);
704   - qpdf_offset_t read_xrefStream(qpdf_offset_t offset, bool in_stream_recovery = false);
705   - qpdf_offset_t processXRefStream(
706   - qpdf_offset_t offset, QPDFObjectHandle& xref_stream, bool in_stream_recovery = false);
707   - std::pair<int, std::array<int, 3>>
708   - processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged);
709   - int processXRefSize(
710   - QPDFObjectHandle& dict,
711   - int entry_size,
712   - std::function<QPDFExc(std::string_view)> damaged);
713   - std::pair<int, std::vector<std::pair<int, int>>> processXRefIndex(
714   - QPDFObjectHandle& dict,
715   - int max_num_entries,
716   - std::function<QPDFExc(std::string_view)> damaged);
717   - void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2);
718   - void insertFreeXrefEntry(QPDFObjGen);
719   - QPDFObjectHandle readTrailer();
720   - QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og);
721   - void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
722   - void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
723   - QPDFObjectHandle
724   - readObjectInStream(qpdf::is::OffsetBuffer& input, int stream_id, int obj_id);
725   - size_t recoverStreamLength(
726   - std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset);
727   -
728   - QPDFObjGen read_object_start(qpdf_offset_t offset);
729   - void readObjectAtOffset(
730   - bool attempt_recovery,
731   - qpdf_offset_t offset,
732   - std::string const& description,
733   - QPDFObjGen exp_og);
734   - void resolveObjectsInStream(int obj_stream_number);
735   - bool isCached(QPDFObjGen og);
736   - bool isUnresolved(QPDFObjGen og);
737   - void setLastObjectDescription(std::string const& description, QPDFObjGen og);
738   -
  351 + protected:
739 352 QPDF& qpdf;
740 353 QPDF::Members* m;
741 354  
742   - Foreign foreign_;
743   - Streams streams_;
744   - }; // class QPDF::Doc::Objects
745   -
746   - // This class is used to represent a PDF Pages tree.
747   - class Pages
748   - {
749   - public:
750   - Pages() = delete;
751   - Pages(Pages const&) = delete;
752   - Pages(Pages&&) = delete;
753   - Pages& operator=(Pages const&) = delete;
754   - Pages& operator=(Pages&&) = delete;
755   - ~Pages() = default;
756   -
757   - Pages(QPDF& qpdf, QPDF::Members* m) :
758   - qpdf(qpdf),
759   - m(m)
760   - {
761   - }
762   -
763   - void getAllPagesInternal(
764   - QPDFObjectHandle cur_pages,
765   - QPDFObjGen::set& visited,
766   - QPDFObjGen::set& seen,
767   - bool media_box,
768   - bool resources);
769   - void insertPage(QPDFObjectHandle newpage, int pos);
770   - void flattenPagesTree();
771   - void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate);
772   - void pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys);
773   - void pushInheritedAttributesToPageInternal(
774   - QPDFObjectHandle,
775   - std::map<std::string, std::vector<QPDFObjectHandle>>&,
776   - bool allow_changes,
777   - bool warn_skipped_keys);
778   -
779   - private:
780   - QPDF& qpdf;
781   - QPDF::Members* m;
782   - }; // class QPDF::Doc::Pages
  355 + QPDF::Doc::Pages& pages;
  356 + };
783 357  
784 358 Doc() = delete;
785 359 Doc(Doc const&) = delete;
... ... @@ -788,32 +362,17 @@ class QPDF::Doc
788 362 Doc& operator=(Doc&&) = delete;
789 363 ~Doc() = default;
790 364  
791   - Doc(QPDF& qpdf, QPDF::Members& m) :
  365 + Doc(QPDF& qpdf, QPDF::Members* m) :
792 366 qpdf(qpdf),
793   - m(m),
794   - lin_(qpdf, &m),
795   - objects_(qpdf, &m),
796   - pages_(qpdf, &m)
  367 + m(m)
797 368 {
798 369 }
799 370  
800   - Linearization&
801   - linearization()
802   - {
803   - return lin_;
804   - };
  371 + inline Linearization& linearization();
805 372  
806   - Objects&
807   - objects()
808   - {
809   - return objects_;
810   - };
  373 + inline Objects& objects();
811 374  
812   - Pages&
813   - pages()
814   - {
815   - return pages_;
816   - }
  375 + inline Pages& pages();
817 376  
818 377 bool reconstructed_xref() const;
819 378  
... ... @@ -864,11 +423,7 @@ class QPDF::Doc
864 423  
865 424 private:
866 425 QPDF& qpdf;
867   - QPDF::Members& m;
868   -
869   - Linearization lin_;
870   - Objects objects_;
871   - Pages pages_;
  426 + QPDF::Members* m;
872 427  
873 428 // Document Helpers;
874 429 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_;
... ... @@ -878,7 +433,528 @@ class QPDF::Doc
878 433 std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels_;
879 434 };
880 435  
881   -class QPDF::Members
  436 +class QPDF::Doc::Encryption
  437 +{
  438 + public:
  439 + // This class holds data read from the encryption dictionary.
  440 + Encryption(
  441 + int V,
  442 + int R,
  443 + int Length_bytes,
  444 + int P,
  445 + std::string const& O,
  446 + std::string const& U,
  447 + std::string const& OE,
  448 + std::string const& UE,
  449 + std::string const& Perms,
  450 + std::string const& id1,
  451 + bool encrypt_metadata) :
  452 + V(V),
  453 + R(R),
  454 + Length_bytes(Length_bytes),
  455 + P(static_cast<unsigned long long>(P)),
  456 + O(O),
  457 + U(U),
  458 + OE(OE),
  459 + UE(UE),
  460 + Perms(Perms),
  461 + id1(id1),
  462 + encrypt_metadata(encrypt_metadata)
  463 + {
  464 + }
  465 + Encryption(int V, int R, int Length_bytes, bool encrypt_metadata) :
  466 + V(V),
  467 + R(R),
  468 + Length_bytes(Length_bytes),
  469 + encrypt_metadata(encrypt_metadata)
  470 + {
  471 + }
  472 +
  473 + int getV() const;
  474 + int getR() const;
  475 + int getLengthBytes() const;
  476 + int getP() const;
  477 + // Bits in P are numbered from 1 as in the PDF spec.
  478 + bool getP(size_t bit) const;
  479 + std::string const& getO() const;
  480 + std::string const& getU() const;
  481 + std::string const& getOE() const;
  482 + std::string const& getUE() const;
  483 + std::string const& getPerms() const;
  484 + std::string const& getId1() const;
  485 + bool getEncryptMetadata() const;
  486 + // Bits in P are numbered from 1 as in the PDF spec.
  487 + void setP(size_t bit, bool val);
  488 + void setP(unsigned long val);
  489 + void setO(std::string const&);
  490 + void setU(std::string const&);
  491 + void setId1(std::string const& val);
  492 + void setV5EncryptionParameters(
  493 + std::string const& O,
  494 + std::string const& OE,
  495 + std::string const& U,
  496 + std::string const& UE,
  497 + std::string const& Perms);
  498 +
  499 + std::string compute_encryption_key(std::string const& password) const;
  500 +
  501 + bool check_owner_password(std::string& user_password, std::string const& owner_password) const;
  502 +
  503 + bool check_user_password(std::string const& user_password) const;
  504 +
  505 + std::string
  506 + recover_encryption_key_with_password(std::string const& password, bool& perms_valid) const;
  507 +
  508 + void compute_encryption_O_U(char const* user_password, char const* owner_password);
  509 +
  510 + std::string
  511 + compute_encryption_parameters_V5(char const* user_password, char const* owner_password);
  512 +
  513 + std::string compute_parameters(char const* user_password, char const* owner_password);
  514 +
  515 + private:
  516 + static constexpr unsigned int OU_key_bytes_V4 = 16; // ( == sizeof(MD5::Digest)
  517 +
  518 + Encryption(Encryption const&) = delete;
  519 + Encryption& operator=(Encryption const&) = delete;
  520 +
  521 + std::string
  522 + hash_V5(std::string const& password, std::string const& salt, std::string const& udata) const;
  523 + std::string
  524 + compute_O_value(std::string const& user_password, std::string const& owner_password) const;
  525 + std::string compute_U_value(std::string const& user_password) const;
  526 + std::string compute_encryption_key_from_password(std::string const& password) const;
  527 + std::string recover_encryption_key_with_password(std::string const& password) const;
  528 + bool
  529 + check_owner_password_V4(std::string& user_password, std::string const& owner_password) const;
  530 + bool check_owner_password_V5(std::string const& owner_passworda) const;
  531 + std::string compute_Perms_value_V5_clear() const;
  532 + std::string
  533 + compute_O_rc4_key(std::string const& user_password, std::string const& owner_password) const;
  534 + std::string compute_U_value_R2(std::string const& user_password) const;
  535 + std::string compute_U_value_R3(std::string const& user_password) const;
  536 + bool check_user_password_V4(std::string const& user_password) const;
  537 + bool check_user_password_V5(std::string const& user_password) const;
  538 +
  539 + int V;
  540 + int R;
  541 + int Length_bytes;
  542 + std::bitset<32> P{0xfffffffc}; // Specification always requires bits 1 and 2 to be cleared.
  543 + std::string O;
  544 + std::string U;
  545 + std::string OE;
  546 + std::string UE;
  547 + std::string Perms;
  548 + std::string id1;
  549 + bool encrypt_metadata;
  550 +}; // class QPDF::Doc::Encryption
  551 +
  552 +class QPDF::Doc::Linearization: Common
  553 +{
  554 + public:
  555 + Linearization() = delete;
  556 + Linearization(Linearization const&) = delete;
  557 + Linearization(Linearization&&) = delete;
  558 + Linearization& operator=(Linearization const&) = delete;
  559 + Linearization& operator=(Linearization&&) = delete;
  560 + ~Linearization() = default;
  561 +
  562 + Linearization(Doc& doc) :
  563 + Common(doc.qpdf, doc.m)
  564 + {
  565 + }
  566 +
  567 + // For QPDFWriter:
  568 +
  569 + template <typename T>
  570 + void optimize_internal(
  571 + T const& object_stream_data,
  572 + bool allow_changes = true,
  573 + std::function<int(QPDFObjectHandle&)> skip_stream_parameters = nullptr);
  574 + void optimize(
  575 + QPDFWriter::ObjTable const& obj,
  576 + std::function<int(QPDFObjectHandle&)> skip_stream_parameters);
  577 +
  578 + // Get lists of all objects in order according to the part of a linearized file that they
  579 + // belong to.
  580 + void getLinearizedParts(
  581 + QPDFWriter::ObjTable const& obj,
  582 + std::vector<QPDFObjectHandle>& part4,
  583 + std::vector<QPDFObjectHandle>& part6,
  584 + std::vector<QPDFObjectHandle>& part7,
  585 + std::vector<QPDFObjectHandle>& part8,
  586 + std::vector<QPDFObjectHandle>& part9);
  587 +
  588 + void generateHintStream(
  589 + QPDFWriter::NewObjTable const& new_obj,
  590 + QPDFWriter::ObjTable const& obj,
  591 + std::string& hint_stream,
  592 + int& S,
  593 + int& O,
  594 + bool compressed);
  595 +
  596 + // methods to support linearization checking -- implemented in QPDF_linearization.cc
  597 +
  598 + void readLinearizationData();
  599 + void checkLinearizationInternal();
  600 + void dumpLinearizationDataInternal();
  601 + void linearizationWarning(std::string_view);
  602 + qpdf::Dictionary readHintStream(Pipeline&, qpdf_offset_t offset, size_t length);
  603 + void readHPageOffset(BitStream);
  604 + void readHSharedObject(BitStream);
  605 + void readHGeneric(BitStream, HGeneric&);
  606 + qpdf_offset_t maxEnd(ObjUser const& ou);
  607 + qpdf_offset_t getLinearizationOffset(QPDFObjGen);
  608 + QPDFObjectHandle
  609 + getUncompressedObject(QPDFObjectHandle&, std::map<int, int> const& object_stream_data);
  610 + QPDFObjectHandle getUncompressedObject(QPDFObjectHandle&, QPDFWriter::ObjTable const& obj);
  611 + int lengthNextN(int first_object, int n);
  612 + void
  613 + checkHPageOffset(std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj);
  614 + void
  615 + checkHSharedObject(std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj);
  616 + void checkHOutlines();
  617 + void dumpHPageOffset();
  618 + void dumpHSharedObject();
  619 + void dumpHGeneric(HGeneric&);
  620 + qpdf_offset_t adjusted_offset(qpdf_offset_t offset);
  621 + template <typename T>
  622 + void calculateLinearizationData(T const& object_stream_data);
  623 + template <typename T>
  624 + void pushOutlinesToPart(
  625 + std::vector<QPDFObjectHandle>& part,
  626 + std::set<QPDFObjGen>& lc_outlines,
  627 + T const& object_stream_data);
  628 + int outputLengthNextN(
  629 + int in_object,
  630 + int n,
  631 + QPDFWriter::NewObjTable const& new_obj,
  632 + QPDFWriter::ObjTable const& obj);
  633 + void
  634 + calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
  635 + void
  636 + calculateHSharedObject(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
  637 + void calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj);
  638 + void writeHPageOffset(BitWriter&);
  639 + void writeHSharedObject(BitWriter&);
  640 + void writeHGeneric(BitWriter&, HGeneric&);
  641 +
  642 + // Methods to support optimization
  643 +
  644 + void updateObjectMaps(
  645 + ObjUser const& ou,
  646 + QPDFObjectHandle oh,
  647 + std::function<int(QPDFObjectHandle&)> skip_stream_parameters);
  648 + void filterCompressedObjects(std::map<int, int> const& object_stream_data);
  649 + void filterCompressedObjects(QPDFWriter::ObjTable const& object_stream_data);
  650 +};
  651 +
  652 +class QPDF::Doc::Objects: Common
  653 +{
  654 + public:
  655 + class Foreign: Common
  656 + {
  657 + class Copier: Common
  658 + {
  659 + public:
  660 + inline Copier(QPDF& qpdf);
  661 +
  662 + QPDFObjectHandle copied(QPDFObjectHandle const& foreign);
  663 +
  664 + private:
  665 + QPDFObjectHandle
  666 + replace_indirect_object(QPDFObjectHandle const& foreign, bool top = false);
  667 + void reserve_objects(QPDFObjectHandle const& foreign, bool top = false);
  668 +
  669 + std::map<QPDFObjGen, QPDFObjectHandle> object_map;
  670 + std::vector<QPDFObjectHandle> to_copy;
  671 + QPDFObjGen::set visiting;
  672 + };
  673 +
  674 + public:
  675 + Foreign(Common& common) :
  676 + Common(common)
  677 + {
  678 + }
  679 +
  680 + Foreign() = delete;
  681 + Foreign(Foreign const&) = delete;
  682 + Foreign(Foreign&&) = delete;
  683 + Foreign& operator=(Foreign const&) = delete;
  684 + Foreign& operator=(Foreign&&) = delete;
  685 + ~Foreign() = default;
  686 +
  687 + // Return a local handle to the foreign object. Copy the foreign object if necessary.
  688 + QPDFObjectHandle
  689 + copied(QPDFObjectHandle const& foreign)
  690 + {
  691 + return copier(foreign).copied(foreign);
  692 + }
  693 +
  694 + private:
  695 + Copier& copier(QPDFObjectHandle const& foreign);
  696 +
  697 + std::map<unsigned long long, Copier> copiers;
  698 + }; // class QPDF::Doc::Objects::Foreign
  699 +
  700 + class Streams: Common
  701 + {
  702 + // Copier manages the copying of streams into this PDF. It is used both for copying
  703 + // local and foreign streams.
  704 + class Copier;
  705 +
  706 + public:
  707 + Streams(Common& common);
  708 +
  709 + Streams() = delete;
  710 + Streams(Streams const&) = delete;
  711 + Streams(Streams&&) = delete;
  712 + Streams& operator=(Streams const&) = delete;
  713 + Streams& operator=(Streams&&) = delete;
  714 + ~Streams() = default;
  715 +
  716 + public:
  717 + static bool
  718 + pipeStreamData(
  719 + QPDF* qpdf,
  720 + QPDFObjGen og,
  721 + qpdf_offset_t offset,
  722 + size_t length,
  723 + QPDFObjectHandle dict,
  724 + bool is_root_metadata,
  725 + Pipeline* pipeline,
  726 + bool suppress_warnings,
  727 + bool will_retry)
  728 + {
  729 + return qpdf->pipeStreamData(
  730 + og,
  731 + offset,
  732 + length,
  733 + dict,
  734 + is_root_metadata,
  735 + pipeline,
  736 + suppress_warnings,
  737 + will_retry);
  738 + }
  739 +
  740 + std::shared_ptr<Copier>&
  741 + copier()
  742 + {
  743 + return copier_;
  744 + }
  745 +
  746 + bool immediate_copy_from() const;
  747 +
  748 + private:
  749 + std::shared_ptr<Copier> copier_;
  750 + }; // class QPDF::Doc::Objects::Streams
  751 +
  752 + public:
  753 + Objects() = delete;
  754 + Objects(Objects const&) = delete;
  755 + Objects(Objects&&) = delete;
  756 + Objects& operator=(Objects const&) = delete;
  757 + Objects& operator=(Objects&&) = delete;
  758 + ~Objects() = default;
  759 +
  760 + Objects(Doc& doc) :
  761 + Common(doc.qpdf, doc.m),
  762 + foreign_(*this),
  763 + streams_(*this)
  764 + {
  765 + }
  766 +
  767 + Foreign&
  768 + foreign()
  769 + {
  770 + return foreign_;
  771 + }
  772 +
  773 + Streams&
  774 + streams()
  775 + {
  776 + return streams_;
  777 + }
  778 +
  779 + void parse(char const* password);
  780 + std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
  781 + void inParse(bool);
  782 + QPDFObjGen nextObjGen();
  783 + QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr<QPDFObject> const&);
  784 + void updateCache(
  785 + QPDFObjGen og,
  786 + std::shared_ptr<QPDFObject> const& object,
  787 + qpdf_offset_t end_before_space,
  788 + qpdf_offset_t end_after_space,
  789 + bool destroy = true);
  790 + bool resolveXRefTable();
  791 + QPDFObjectHandle readObjectAtOffset(
  792 + qpdf_offset_t offset, std::string const& description, bool skip_cache_if_in_xref);
  793 + QPDFTokenizer::Token readToken(InputSource& input, size_t max_len = 0);
  794 + QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj);
  795 + std::shared_ptr<QPDFObject> getObjectForParser(int id, int gen, bool parse_pdf);
  796 + std::shared_ptr<QPDFObject> getObjectForJSON(int id, int gen);
  797 + size_t tableSize();
  798 +
  799 + // For QPDFWriter:
  800 +
  801 + std::map<QPDFObjGen, QPDFXRefEntry> const& getXRefTableInternal();
  802 + // Get a list of objects that would be permitted in an object stream.
  803 + template <typename T>
  804 + std::vector<T> getCompressibleObjGens();
  805 + std::vector<QPDFObjGen> getCompressibleObjVector();
  806 + std::vector<bool> getCompressibleObjSet();
  807 +
  808 + private:
  809 + void setTrailer(QPDFObjectHandle obj);
  810 + void reconstruct_xref(QPDFExc& e, bool found_startxref = true);
  811 + void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false);
  812 + bool parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes);
  813 + bool read_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
  814 + bool read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& type);
  815 + qpdf_offset_t read_xrefTable(qpdf_offset_t offset);
  816 + qpdf_offset_t read_xrefStream(qpdf_offset_t offset, bool in_stream_recovery = false);
  817 + qpdf_offset_t processXRefStream(
  818 + qpdf_offset_t offset, QPDFObjectHandle& xref_stream, bool in_stream_recovery = false);
  819 + std::pair<int, std::array<int, 3>>
  820 + processXRefW(QPDFObjectHandle& dict, std::function<QPDFExc(std::string_view)> damaged);
  821 + int processXRefSize(
  822 + QPDFObjectHandle& dict, int entry_size, std::function<QPDFExc(std::string_view)> damaged);
  823 + std::pair<int, std::vector<std::pair<int, int>>> processXRefIndex(
  824 + QPDFObjectHandle& dict,
  825 + int max_num_entries,
  826 + std::function<QPDFExc(std::string_view)> damaged);
  827 + void insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2);
  828 + void insertFreeXrefEntry(QPDFObjGen);
  829 + QPDFObjectHandle readTrailer();
  830 + QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og);
  831 + void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
  832 + void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset);
  833 + QPDFObjectHandle readObjectInStream(qpdf::is::OffsetBuffer& input, int stream_id, int obj_id);
  834 + size_t recoverStreamLength(
  835 + std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset);
  836 +
  837 + QPDFObjGen read_object_start(qpdf_offset_t offset);
  838 + void readObjectAtOffset(
  839 + bool attempt_recovery,
  840 + qpdf_offset_t offset,
  841 + std::string const& description,
  842 + QPDFObjGen exp_og);
  843 + void resolveObjectsInStream(int obj_stream_number);
  844 + bool isCached(QPDFObjGen og);
  845 + bool isUnresolved(QPDFObjGen og);
  846 + void setLastObjectDescription(std::string const& description, QPDFObjGen og);
  847 +
  848 + Foreign foreign_;
  849 + Streams streams_;
  850 +}; // class QPDF::Doc::Objects
  851 +
  852 +// This class is used to represent a PDF Pages tree.
  853 +class QPDF::Doc::Pages: Common
  854 +{
  855 + public:
  856 + using iterator = std::vector<QPDFObjectHandle>::const_iterator;
  857 +
  858 + Pages() = delete;
  859 + Pages(Pages const&) = delete;
  860 + Pages(Pages&&) = delete;
  861 + Pages& operator=(Pages const&) = delete;
  862 + Pages& operator=(Pages&&) = delete;
  863 + ~Pages() = default;
  864 +
  865 + Pages(Doc& doc) :
  866 + Common(doc.qpdf, doc.m)
  867 + {
  868 + }
  869 +
  870 + std::vector<QPDFObjectHandle> const&
  871 + all()
  872 + {
  873 + return !all_pages.empty() ? all_pages : cache();
  874 + }
  875 +
  876 + bool
  877 + empty()
  878 + {
  879 + return all().empty();
  880 + }
  881 +
  882 + size_t
  883 + size()
  884 + {
  885 + return all().size();
  886 + }
  887 +
  888 + iterator
  889 + begin()
  890 + {
  891 + return all().cbegin();
  892 + }
  893 +
  894 + iterator
  895 + end()
  896 + {
  897 + return all().cend();
  898 + }
  899 +
  900 + int find(QPDFObjGen og);
  901 + void insert(QPDFObjectHandle newpage, int pos);
  902 + void
  903 + insert(QPDFObjectHandle const& newpage, size_t pos)
  904 + {
  905 + insert(newpage, static_cast<int>(pos));
  906 + }
  907 + void erase(QPDFObjectHandle& page);
  908 + void update_cache();
  909 + void flatten_annotations(int required_flags, int forbidden_flags);
  910 +
  911 + bool
  912 + ever_pushed_inherited_attributes_to_pages() const
  913 + {
  914 + return ever_pushed_inherited_attributes_to_pages_;
  915 + }
  916 +
  917 + bool
  918 + ever_called_get_all_pages() const
  919 + {
  920 + return ever_called_get_all_pages_;
  921 + }
  922 +
  923 + void pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys);
  924 +
  925 + private:
  926 + void flattenPagesTree();
  927 + void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate);
  928 + void pushInheritedAttributesToPageInternal(
  929 + QPDFObjectHandle,
  930 + std::map<std::string, std::vector<QPDFObjectHandle>>&,
  931 + bool allow_changes,
  932 + bool warn_skipped_keys);
  933 + std::vector<QPDFObjectHandle> const& cache();
  934 + void getAllPagesInternal(
  935 + QPDFObjectHandle cur_pages,
  936 + QPDFObjGen::set& visited,
  937 + QPDFObjGen::set& seen,
  938 + bool media_box,
  939 + bool resources);
  940 + void flatten_annotations_for_page(
  941 + QPDFPageObjectHelper& page,
  942 + QPDFObjectHandle& resources,
  943 + QPDFAcroFormDocumentHelper& afdh,
  944 + int required_flags,
  945 + int forbidden_flags);
  946 +
  947 + std::vector<QPDFObjectHandle> all_pages;
  948 + std::map<QPDFObjGen, int> pageobj_to_pages_pos;
  949 +
  950 + bool pushed_inherited_attributes_to_pages{false};
  951 + bool invalid_page_found{false};
  952 + bool ever_pushed_inherited_attributes_to_pages_{false};
  953 + bool ever_called_get_all_pages_{false};
  954 +
  955 +}; // class QPDF::Doc::Pages
  956 +
  957 +class QPDF::Members: Doc
882 958 {
883 959 friend class QPDF;
884 960 friend class ResolveRecorder;
... ... @@ -889,10 +965,10 @@ class QPDF::Members
889 965 ~Members() = default;
890 966  
891 967 private:
892   - Doc doc;
893   - Doc::Linearization& lin;
894   - Doc::Objects& objects;
895   - Doc::Pages& pages;
  968 + Doc::Common c;
  969 + Doc::Linearization lin;
  970 + Doc::Objects objects;
  971 + Doc::Pages pages;
896 972 std::shared_ptr<QPDFLogger> log;
897 973 unsigned long long unique_id{0};
898 974 qpdf::Tokenizer tokenizer;
... ... @@ -915,12 +991,6 @@ class QPDF::Members
915 991 std::map<QPDFObjGen, ObjCache> obj_cache;
916 992 std::set<QPDFObjGen> resolving;
917 993 QPDFObjectHandle trailer;
918   - std::vector<QPDFObjectHandle> all_pages;
919   - bool invalid_page_found{false};
920   - std::map<QPDFObjGen, int> pageobj_to_pages_pos;
921   - bool pushed_inherited_attributes_to_pages{false};
922   - bool ever_pushed_inherited_attributes_to_pages{false};
923   - bool ever_called_get_all_pages{false};
924 994 std::vector<QPDFExc> warnings;
925 995 bool reconstructed_xref{false};
926 996 bool in_read_xref_stream{false};
... ... @@ -978,25 +1048,46 @@ class QPDF::Doc::Resolver
978 1048 }
979 1049 };
980 1050  
  1051 +inline QPDF::Doc::Common::Common(QPDF& qpdf, QPDF::Members* m) :
  1052 + qpdf(qpdf),
  1053 + m(m),
  1054 + pages(m->pages)
  1055 +{
  1056 +}
  1057 +
  1058 +inline QPDF::Doc::Linearization&
  1059 +QPDF::Doc::linearization()
  1060 +{
  1061 + return m->lin;
  1062 +};
  1063 +
  1064 +inline QPDF::Doc::Objects&
  1065 +QPDF::Doc::objects()
  1066 +{
  1067 + return m->objects;
  1068 +};
  1069 +
  1070 +inline QPDF::Doc::Pages&
  1071 +QPDF::Doc::pages()
  1072 +{
  1073 + return m->pages;
  1074 +}
  1075 +
981 1076 inline bool
982 1077 QPDF::Doc::reconstructed_xref() const
983 1078 {
984   - return m.reconstructed_xref;
  1079 + return m->reconstructed_xref;
985 1080 }
986 1081  
987 1082 inline QPDF::Doc&
988 1083 QPDF::doc()
989 1084 {
990   - return m->doc;
  1085 + return *m;
991 1086 }
992 1087  
993   -// Throw a generic exception for unusual error conditions that do not be covered during CI testing.
994   -inline void
995   -QPDF::no_ci_stop_if(bool condition, std::string const& message, std::string const& context)
  1088 +inline QPDF::Doc::Objects::Foreign::Copier::Copier(QPDF& qpdf) :
  1089 + Common(qpdf, qpdf.doc().m)
996 1090 {
997   - if (condition) {
998   - throw damagedPDF(context, message);
999   - }
1000 1091 }
1001 1092  
1002 1093 #endif // QPDF_PRIVATE_HH
... ...
qpdf/qpdf.testcov
... ... @@ -224,14 +224,8 @@ QPDFAnnotationObjectHelper default matrix 0
224 224 QPDFAnnotationObjectHelper rotate 90 0
225 225 QPDFAnnotationObjectHelper rotate 180 0
226 226 QPDFAnnotationObjectHelper rotate 270 0
227   -QPDFPageDocumentHelper skip widget need appearances 0
228   -QPDFPageDocumentHelper merge DR 0
229 227 QPDFPageDocumentHelper non-widget annotation 0
230   -QPDFPageDocumentHelper remove annots 0
231   -QPDFPageDocumentHelper replace indirect annots 0
232   -QPDFPageDocumentHelper replace direct annots 0
233 228 QPDFObjectHandle replace with copy 0
234   -QPDFPageDocumentHelper indirect as resources 0
235 229 QPDFAnnotationObjectHelper forbidden flags 0
236 230 QPDFAnnotationObjectHelper missing required flags 0
237 231 QPDFFormFieldObjectHelper checkbox kid widget 0
... ... @@ -255,7 +249,6 @@ QPDFJob auto-encode password 0
255 249 QPDFJob bytes fallback warning 0
256 250 QPDFJob invalid utf-8 in auto 0
257 251 QPDFJob input password hex-bytes 0
258   -QPDFPageDocumentHelper ignore annotation with no appearance 0
259 252 QPDFFormFieldObjectHelper replaced BMC at EOF 0
260 253 QPDFFormFieldObjectHelper fallback Tf 0
261 254 QPDFPageObjectHelper copy shared attribute 1
... ... @@ -383,8 +376,6 @@ qpdf-c warn about oh error 1
383 376 qpdf-c cleanup warned about unhandled error 0
384 377 qpdf-c called qpdf_get_object_by_id 0
385 378 qpdf-c called qpdf_replace_object 0
386   -qpdf-c called qpdf_num_pages 0
387   -qpdf-c called qpdf_get_page_n 0
388 379 qpdf-c called qpdf_update_all_pages_cache 0
389 380 qpdf-c called qpdf_find_page_by_id 0
390 381 qpdf-c called qpdf_find_page_by_oh 0
... ... @@ -422,14 +413,9 @@ qpdf-c called qpdf_empty_pdf 0
422 413 QPDF_json missing qpdf 0
423 414 QPDF_json missing pdf version 0
424 415 QPDF_json top-level scalar 0
425   -QPDF_json bad pdf version 0
426 416 QPDF_json top-level array 0
427   -QPDF_json bad object key 0
428   -QPDF_json trailer stream 0
429 417 QPDF_json missing trailer 0
430 418 QPDF_json missing objects 0
431   -QPDF_json ignoring in st_ignore 0
432   -QPDF_json stream dict not dict 0
433 419 QPDF_json unrecognized string value 0
434 420 QPDF_json data datafile both or neither 0
435 421 QPDF_json stream no dict 0
... ... @@ -438,25 +424,13 @@ QPDF_json value stream both or neither 0
438 424 QPDFJob need json-stream-prefix for stdout 0
439 425 QPDFJob write json to stdout 0
440 426 QPDFJob write json to file 0
441   -QPDF_json ignoring unknown top-level key 0
442   -QPDF_json ignore second-level key 0
443   -QPDF_json ignore unknown key in object_top 0
444   -QPDF_json ignore unknown key in trailer 0
445   -QPDF_json ignore unknown key in stream 0
446 427 QPDF_json data and datafile 0
447 428 QPDF_json no stream data in update mode 0
448 429 QPDF_json updating existing stream 0
449   -QPDF_json qpdf not array 0
450 430 QPDF_json more than two qpdf elements 0
451 431 QPDF_json missing json version 0
452   -QPDF_json bad json version 0
453   -QPDF_json bad calledgetallpages 0
454   -QPDF_json bad pushedinheritedpageresources 0
455 432 QPDFPageObjectHelper used fallback without copying 0
456 433 QPDF skipping cache for known unchecked object 0
457 434 QPDF recover xref stream 0
458 435 QPDFJob json over/under no file 0
459 436 QPDF_Array copy 1
460   -QPDF_json stream data not string 0
461   -QPDF_json stream datafile not string 0
462   -QPDF_json stream not a dictionary 0
... ...