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,20 +747,7 @@ class QPDF
747 class ResolveRecorder; 747 class ResolveRecorder;
748 class JSONReactor; 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 void removeObject(QPDFObjGen og); 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 // Calls finish() on the pipeline when done but does not delete it 752 // Calls finish() on the pipeline when done but does not delete it
766 bool pipeStreamData( 753 bool pipeStreamData(
include/qpdf/QPDFPageDocumentHelper.hh
@@ -120,13 +120,6 @@ class QPDFPageDocumentHelper: public QPDFDocumentHelper @@ -120,13 +120,6 @@ class QPDFPageDocumentHelper: public QPDFDocumentHelper
120 void flattenAnnotations(int required_flags = 0, int forbidden_flags = an_invisible | an_hidden); 120 void flattenAnnotations(int required_flags = 0, int forbidden_flags = an_invisible | an_hidden);
121 121
122 private: 122 private:
123 - void flattenAnnotationsForPage(  
124 - QPDFPageObjectHelper& page,  
125 - QPDFObjectHandle& resources,  
126 - QPDFAcroFormDocumentHelper& afdh,  
127 - int required_flags,  
128 - int forbidden_flags);  
129 -  
130 class Members; 123 class Members;
131 124
132 std::shared_ptr<Members> m; 125 std::shared_ptr<Members> m;
libqpdf/CMakeLists.txt
@@ -76,7 +76,6 @@ set(libqpdf_SOURCES @@ -76,7 +76,6 @@ set(libqpdf_SOURCES
76 QPDFObjectHelper.cc 76 QPDFObjectHelper.cc
77 QPDFOutlineDocumentHelper.cc 77 QPDFOutlineDocumentHelper.cc
78 QPDFOutlineObjectHelper.cc 78 QPDFOutlineObjectHelper.cc
79 - QPDFPageDocumentHelper.cc  
80 QPDFPageLabelDocumentHelper.cc 79 QPDFPageLabelDocumentHelper.cc
81 QPDFPageObjectHelper.cc 80 QPDFPageObjectHelper.cc
82 QPDFParser.cc 81 QPDFParser.cc
@@ -94,7 +93,6 @@ set(libqpdf_SOURCES @@ -94,7 +93,6 @@ set(libqpdf_SOURCES
94 QPDF_json.cc 93 QPDF_json.cc
95 QPDF_linearization.cc 94 QPDF_linearization.cc
96 QPDF_objects.cc 95 QPDF_objects.cc
97 - QPDF_optimization.cc  
98 QPDF_pages.cc 96 QPDF_pages.cc
99 QTC.cc 97 QTC.cc
100 QUtil.cc 98 QUtil.cc
libqpdf/QPDF.cc
@@ -27,7 +27,9 @@ @@ -27,7 +27,9 @@
27 using namespace qpdf; 27 using namespace qpdf;
28 using namespace std::literals; 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 using Foreign = Objects::Foreign; 33 using Foreign = Objects::Foreign;
32 using Streams = Objects::Streams; 34 using Streams = Objects::Streams;
33 35
@@ -126,10 +128,11 @@ QPDF::QPDFVersion() @@ -126,10 +128,11 @@ QPDF::QPDFVersion()
126 } 128 }
127 129
128 QPDF::Members::Members(QPDF& qpdf) : 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 log(QPDFLogger::defaultLogger()), 136 log(QPDFLogger::defaultLogger()),
134 file(std::make_shared<InvalidInputSource>()), 137 file(std::make_shared<InvalidInputSource>()),
135 encp(std::make_shared<EncryptionParameters>()) 138 encp(std::make_shared<EncryptionParameters>())
@@ -363,6 +366,12 @@ QPDF::findHeader() @@ -363,6 +366,12 @@ QPDF::findHeader()
363 void 366 void
364 QPDF::warn(QPDFExc const& e) 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 if (m->max_warnings > 0 && m->warnings.size() >= m->max_warnings) { 375 if (m->max_warnings > 0 && m->warnings.size() >= m->max_warnings) {
367 stopOnError("Too many warnings - file is too badly damaged"); 376 stopOnError("Too many warnings - file is too badly damaged");
368 } 377 }
@@ -379,7 +388,17 @@ QPDF::warn( @@ -379,7 +388,17 @@ QPDF::warn(
379 qpdf_offset_t offset, 388 qpdf_offset_t offset,
380 std::string const& message) 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 QPDFObjectHandle 404 QPDFObjectHandle
@@ -514,7 +533,7 @@ Objects::Foreign::Copier::copied(QPDFObjectHandle const&amp; foreign) @@ -514,7 +533,7 @@ Objects::Foreign::Copier::copied(QPDFObjectHandle const&amp; foreign)
514 533
515 auto og = foreign.getObjGen(); 534 auto og = foreign.getObjGen();
516 if (!object_map.contains(og)) { 535 if (!object_map.contains(og)) {
517 - qpdf.warn(qpdf.damagedPDF( 536 + warn(damagedPDF(
518 foreign.qpdf()->getFilename() + " object " + og.unparse(' '), 537 foreign.qpdf()->getFilename() + " object " + og.unparse(' '),
519 foreign.offset(), 538 foreign.offset(),
520 "unexpected reference to /Pages object while copying foreign object; replacing with " 539 "unexpected reference to /Pages object while copying foreign object; replacing with "
@@ -692,12 +711,12 @@ QPDF::getRoot() @@ -692,12 +711,12 @@ QPDF::getRoot()
692 { 711 {
693 QPDFObjectHandle root = m->trailer.getKey("/Root"); 712 QPDFObjectHandle root = m->trailer.getKey("/Root");
694 if (!root.isDictionary()) { 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 } else if ( 715 } else if (
697 // Check_mode is an interim solution to request #810 pending a more comprehensive review of 716 // Check_mode is an interim solution to request #810 pending a more comprehensive review of
698 // the approach to more extensive checks and warning levels. 717 // the approach to more extensive checks and warning levels.
699 m->check_mode && !root.getKey("/Type").isNameAndEquals("/Catalog")) { 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 root.replaceKey("/Type", "/Catalog"_qpdf); 720 root.replaceKey("/Type", "/Catalog"_qpdf);
702 } 721 }
703 return root; 722 return root;
@@ -743,7 +762,7 @@ QPDF::pipeStreamData( @@ -743,7 +762,7 @@ QPDF::pipeStreamData(
743 try { 762 try {
744 auto buf = file->read(length, offset); 763 auto buf = file->read(length, offset);
745 if (buf.size() != length) { 764 if (buf.size() != length) {
746 - throw damagedPDF( 765 + throw qpdf_for_warning.m->c.damagedPDF(
747 *file, "", offset + toO(buf.size()), "unexpected EOF reading stream data"); 766 *file, "", offset + toO(buf.size()), "unexpected EOF reading stream data");
748 } 767 }
749 pipeline->write(buf.data(), length); 768 pipeline->write(buf.data(), length);
@@ -759,7 +778,7 @@ QPDF::pipeStreamData( @@ -759,7 +778,7 @@ QPDF::pipeStreamData(
759 QTC::TC("qpdf", "QPDF decoding error warning"); 778 QTC::TC("qpdf", "QPDF decoding error warning");
760 qpdf_for_warning.warn( 779 qpdf_for_warning.warn(
761 // line-break 780 // line-break
762 - damagedPDF( 781 + qpdf_for_warning.m->c.damagedPDF(
763 *file, 782 *file,
764 "", 783 "",
765 file->getLastOffset(), 784 file->getLastOffset(),
@@ -768,7 +787,7 @@ QPDF::pipeStreamData( @@ -768,7 +787,7 @@ QPDF::pipeStreamData(
768 if (will_retry) { 787 if (will_retry) {
769 qpdf_for_warning.warn( 788 qpdf_for_warning.warn(
770 // line-break 789 // line-break
771 - damagedPDF( 790 + qpdf_for_warning.m->c.damagedPDF(
772 *file, 791 *file,
773 "", 792 "",
774 file->getLastOffset(), 793 file->getLastOffset(),
@@ -814,14 +833,14 @@ QPDF::pipeStreamData( @@ -814,14 +833,14 @@ QPDF::pipeStreamData(
814 // Throw a generic exception when we lack context for something more specific. New code should not 833 // Throw a generic exception when we lack context for something more specific. New code should not
815 // use this. 834 // use this.
816 void 835 void
817 -QPDF::stopOnError(std::string const& message) 836 +Common::stopOnError(std::string const& message)
818 { 837 {
819 throw damagedPDF("", message); 838 throw damagedPDF("", message);
820 } 839 }
821 840
822 // Return an exception of type qpdf_e_damaged_pdf. 841 // Return an exception of type qpdf_e_damaged_pdf.
823 QPDFExc 842 QPDFExc
824 -QPDF::damagedPDF( 843 +Common::damagedPDF(
825 InputSource& input, std::string const& object, qpdf_offset_t offset, std::string const& message) 844 InputSource& input, std::string const& object, qpdf_offset_t offset, std::string const& message)
826 { 845 {
827 return {qpdf_e_damaged_pdf, input.getName(), object, offset, message, true}; 846 return {qpdf_e_damaged_pdf, input.getName(), object, offset, message, true};
@@ -830,14 +849,15 @@ QPDF::damagedPDF( @@ -830,14 +849,15 @@ QPDF::damagedPDF(
830 // Return an exception of type qpdf_e_damaged_pdf. The object is taken from 849 // Return an exception of type qpdf_e_damaged_pdf. The object is taken from
831 // m->last_object_description. 850 // m->last_object_description.
832 QPDFExc 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 return damagedPDF(input, m->last_object_description, offset, message); 854 return damagedPDF(input, m->last_object_description, offset, message);
836 } 855 }
837 856
838 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file. 857 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file.
839 QPDFExc 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 return {qpdf_e_damaged_pdf, m->file->getName(), object, offset, message, true}; 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,7 +865,7 @@ QPDF::damagedPDF(std::string const&amp; object, qpdf_offset_t offset, std::string co
845 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file and the 865 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file and the
846 // offset from .m->file->getLastOffset(). 866 // offset from .m->file->getLastOffset().
847 QPDFExc 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 return damagedPDF(object, m->file->getLastOffset(), message); 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,7 +873,7 @@ QPDF::damagedPDF(std::string const&amp; object, std::string const&amp; message)
853 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file and the object 873 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file and the object
854 // from .m->last_object_description. 874 // from .m->last_object_description.
855 QPDFExc 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 return damagedPDF(m->last_object_description, offset, message); 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,7 +881,7 @@ QPDF::damagedPDF(qpdf_offset_t offset, std::string const&amp; message)
861 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file, the object 881 // Return an exception of type qpdf_e_damaged_pdf. The filename is taken from m->file, the object
862 // from m->last_object_description and the offset from m->file->getLastOffset(). 882 // from m->last_object_description and the offset from m->file->getLastOffset().
863 QPDFExc 883 QPDFExc
864 -QPDF::damagedPDF(std::string const& message) 884 +Common::damagedPDF(std::string const& message) const
865 { 885 {
866 return damagedPDF(m->last_object_description, m->file->getLastOffset(), message); 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,13 +889,13 @@ QPDF::damagedPDF(std::string const&amp; message)
869 bool 889 bool
870 QPDF::everCalledGetAllPages() const 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 bool 895 bool
876 QPDF::everPushedInheritedAttributesToPages() const 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 void 901 void
libqpdf/QPDFJob.cc
@@ -30,8 +30,11 @@ @@ -30,8 +30,11 @@
30 30
31 using namespace qpdf; 31 using namespace qpdf;
32 32
  33 +using Doc = QPDF::Doc;
  34 +using Pages = Doc::Pages;
  35 +
33 // JobSetter class is restricted to QPDFJob. 36 // JobSetter class is restricted to QPDFJob.
34 -class QPDF::Doc::JobSetter 37 +class Doc::JobSetter
35 { 38 {
36 public: 39 public:
37 // Enable enhanced warnings for pdf file checking. 40 // Enable enhanced warnings for pdf file checking.
@@ -746,7 +749,7 @@ QPDFJob::doCheck(QPDF&amp; pdf) @@ -746,7 +749,7 @@ QPDFJob::doCheck(QPDF&amp; pdf)
746 bool okay = true; 749 bool okay = true;
747 auto& cout = *m->log->getInfo(); 750 auto& cout = *m->log->getInfo();
748 cout << "checking " << m->infile_name() << "\n"; 751 cout << "checking " << m->infile_name() << "\n";
749 - QPDF::Doc::JobSetter::setCheckMode(pdf, true); 752 + Doc::JobSetter::setCheckMode(pdf, true);
750 try { 753 try {
751 int extension_level = pdf.getExtensionLevel(); 754 int extension_level = pdf.getExtensionLevel();
752 cout << "PDF Version: " << pdf.getPDFVersion(); 755 cout << "PDF Version: " << pdf.getPDFVersion();
@@ -852,7 +855,7 @@ QPDFJob::doShowPages(QPDF&amp; pdf) @@ -852,7 +855,7 @@ QPDFJob::doShowPages(QPDF&amp; pdf)
852 { 855 {
853 int pageno = 0; 856 int pageno = 0;
854 auto& cout = *m->log->getInfo(); 857 auto& cout = *m->log->getInfo();
855 - for (auto& page: pdf.getAllPages()) { 858 + for (auto& page: pdf.doc().pages()) {
856 QPDFPageObjectHelper ph(page); 859 QPDFPageObjectHelper ph(page);
857 ++pageno; 860 ++pageno;
858 861
@@ -1040,13 +1043,14 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool&amp; first, QPDF&amp; pdf) @@ -1040,13 +1043,14 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1040 void 1043 void
1041 QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf) 1044 QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf)
1042 { 1045 {
  1046 + auto& doc = pdf.doc();
1043 JSON::writeDictionaryKey(p, first, "pages", 1); 1047 JSON::writeDictionaryKey(p, first, "pages", 1);
1044 bool first_page = true; 1048 bool first_page = true;
1045 JSON::writeArrayOpen(p, first_page, 2); 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 int pageno = -1; 1052 int pageno = -1;
1049 - for (auto& page: pdf.getAllPages()) { 1053 + for (auto& page: doc.pages()) {
1050 ++pageno; 1054 ++pageno;
1051 JSON j_page = JSON::makeDictionary(); 1055 JSON j_page = JSON::makeDictionary();
1052 QPDFPageObjectHelper ph(page); 1056 QPDFPageObjectHelper ph(page);
@@ -1105,9 +1109,10 @@ QPDFJob::doJSONPages(Pipeline* p, bool&amp; first, QPDF&amp; pdf) @@ -1105,9 +1109,10 @@ QPDFJob::doJSONPages(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1105 void 1109 void
1106 QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf) 1110 QPDFJob::doJSONPageLabels(Pipeline* p, bool& first, QPDF& pdf)
1107 { 1111 {
  1112 + auto& doc = pdf.doc();
1108 JSON j_labels = JSON::makeArray(); 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 if (pldh.hasPageLabels()) { 1116 if (pldh.hasPageLabels()) {
1112 std::vector<QPDFObjectHandle> labels; 1117 std::vector<QPDFObjectHandle> labels;
1113 pldh.getLabelsForPageRange(0, npages - 1, 0, labels); 1118 pldh.getLabelsForPageRange(0, npages - 1, 0, labels);
@@ -1153,27 +1158,29 @@ QPDFJob::addOutlinesToJson( @@ -1153,27 +1158,29 @@ QPDFJob::addOutlinesToJson(
1153 void 1158 void
1154 QPDFJob::doJSONOutlines(Pipeline* p, bool& first, QPDF& pdf) 1159 QPDFJob::doJSONOutlines(Pipeline* p, bool& first, QPDF& pdf)
1155 { 1160 {
  1161 + auto& doc = pdf.doc();
1156 std::map<QPDFObjGen, int> page_numbers; 1162 std::map<QPDFObjGen, int> page_numbers;
1157 int n = 0; 1163 int n = 0;
1158 - for (auto const& oh: pdf.getAllPages()) { 1164 + for (auto const& oh: doc.pages()) {
1159 page_numbers[oh] = ++n; 1165 page_numbers[oh] = ++n;
1160 } 1166 }
1161 1167
1162 JSON j_outlines = JSON::makeArray(); 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 JSON::writeDictionaryItem(p, first, "outlines", j_outlines, 1); 1170 JSON::writeDictionaryItem(p, first, "outlines", j_outlines, 1);
1165 } 1171 }
1166 1172
1167 void 1173 void
1168 QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf) 1174 QPDFJob::doJSONAcroform(Pipeline* p, bool& first, QPDF& pdf)
1169 { 1175 {
  1176 + auto& doc = pdf.doc();
1170 JSON j_acroform = JSON::makeDictionary(); 1177 JSON j_acroform = JSON::makeDictionary();
1171 - auto& afdh = pdf.doc().acroform(); 1178 + auto& afdh = doc.acroform();
1172 j_acroform.addDictionaryMember("hasacroform", JSON::makeBool(afdh.hasAcroForm())); 1179 j_acroform.addDictionaryMember("hasacroform", JSON::makeBool(afdh.hasAcroForm()));
1173 j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances())); 1180 j_acroform.addDictionaryMember("needappearances", JSON::makeBool(afdh.getNeedAppearances()));
1174 JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray()); 1181 JSON j_fields = j_acroform.addDictionaryMember("fields", JSON::makeArray());
1175 int pagepos1 = 0; 1182 int pagepos1 = 0;
1176 - for (auto const& page: pdf.getAllPages()) { 1183 + for (auto const& page: doc.pages()) {
1177 ++pagepos1; 1184 ++pagepos1;
1178 for (auto& aoh: afdh.getWidgetAnnotationsForPage({page})) { 1185 for (auto& aoh: afdh.getWidgetAnnotationsForPage({page})) {
1179 QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh); 1186 QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(aoh);
@@ -1852,7 +1859,7 @@ QPDFJob::validateUnderOverlay(QPDF&amp; pdf, UnderOverlay* uo) @@ -1852,7 +1859,7 @@ QPDFJob::validateUnderOverlay(QPDF&amp; pdf, UnderOverlay* uo)
1852 processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false); 1859 processFile(uo->pdf, uo->filename.data(), uo->password.data(), true, false);
1853 try { 1860 try {
1854 uo->to_pagenos = 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 } catch (std::runtime_error& e) { 1863 } catch (std::runtime_error& e) {
1857 throw std::runtime_error( 1864 throw std::runtime_error(
1858 "parsing numeric range for " + uo->which + " \"to\" pages: " + e.what()); 1865 "parsing numeric range for " + uo->which + " \"to\" pages: " + e.what());
@@ -1861,7 +1868,7 @@ QPDFJob::validateUnderOverlay(QPDF&amp; pdf, UnderOverlay* uo) @@ -1861,7 +1868,7 @@ QPDFJob::validateUnderOverlay(QPDF&amp; pdf, UnderOverlay* uo)
1861 if (uo->from_nr.empty()) { 1868 if (uo->from_nr.empty()) {
1862 uo->from_nr = uo->repeat_nr; 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 uo->from_pagenos = QUtil::parse_numrange(uo->from_nr.data(), uo_npages); 1872 uo->from_pagenos = QUtil::parse_numrange(uo->from_nr.data(), uo_npages);
1866 if (!uo->repeat_nr.empty()) { 1873 if (!uo->repeat_nr.empty()) {
1867 uo->repeat_pagenos = QUtil::parse_numrange(uo->repeat_nr.data(), uo_npages); 1874 uo->repeat_pagenos = QUtil::parse_numrange(uo->repeat_nr.data(), uo_npages);
@@ -1887,7 +1894,7 @@ QPDFJob::doUnderOverlayForPage( @@ -1887,7 +1894,7 @@ QPDFJob::doUnderOverlayForPage(
1887 } 1894 }
1888 auto& dest_afdh = dest_page.qpdf()->doc().acroform(); 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 std::string content; 1898 std::string content;
1892 int min_suffix = 1; 1899 int min_suffix = 1;
1893 QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true); 1900 QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true);
@@ -1957,7 +1964,7 @@ QPDFJob::handleUnderOverlay(QPDF&amp; pdf) @@ -1957,7 +1964,7 @@ QPDFJob::handleUnderOverlay(QPDF&amp; pdf)
1957 validateUnderOverlay(pdf, &uo); 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 // First vector key is 0-based page number. Second is index into the overlay/underlay vector. 1969 // First vector key is 0-based page number. Second is index into the overlay/underlay vector.
1963 // Watch out to not reverse the keys or be off by one. 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,14 +2356,15 @@ QPDFJob::Input::initialize(Inputs&amp; in, QPDF* a_qpdf)
2349 { 2356 {
2350 qpdf = a_qpdf ? a_qpdf : qpdf_p.get(); 2357 qpdf = a_qpdf ? a_qpdf : qpdf_p.get();
2351 if (qpdf) { 2358 if (qpdf) {
2352 - orig_pages = qpdf->getAllPages(); 2359 + auto& doc = qpdf->doc();
  2360 + orig_pages = doc.pages().all();
2353 n_pages = static_cast<int>(orig_pages.size()); 2361 n_pages = static_cast<int>(orig_pages.size());
2354 copied_pages = std::vector<bool>(orig_pages.size(), false); 2362 copied_pages = std::vector<bool>(orig_pages.size(), false);
2355 2363
2356 if (in.job.m->remove_unreferenced_page_resources != QPDFJob::re_no) { 2364 if (in.job.m->remove_unreferenced_page_resources != QPDFJob::re_no) {
2357 remove_unreferenced = in.job.shouldRemoveUnreferencedResources(*qpdf); 2365 remove_unreferenced = in.job.shouldRemoveUnreferencedResources(*qpdf);
2358 } 2366 }
2359 - if (qpdf->doc().page_labels().hasPageLabels()) { 2367 + if (doc.page_labels().hasPageLabels()) {
2360 in.any_page_labels = true; 2368 in.any_page_labels = true;
2361 } 2369 }
2362 } 2370 }
@@ -3001,6 +3009,8 @@ QPDFJob::setWriterOptions(QPDFWriter&amp; w) @@ -3001,6 +3009,8 @@ QPDFJob::setWriterOptions(QPDFWriter&amp; w)
3001 void 3009 void
3002 QPDFJob::doSplitPages(QPDF& pdf) 3010 QPDFJob::doSplitPages(QPDF& pdf)
3003 { 3011 {
  3012 + auto& doc = pdf.doc();
  3013 +
3004 // Generate output file pattern 3014 // Generate output file pattern
3005 std::string before; 3015 std::string before;
3006 std::string after; 3016 std::string after;
@@ -3024,9 +3034,9 @@ QPDFJob::doSplitPages(QPDF&amp; pdf) @@ -3024,9 +3034,9 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3024 QPDFPageDocumentHelper dh(pdf); 3034 QPDFPageDocumentHelper dh(pdf);
3025 dh.removeUnreferencedResources(); 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 size_t pageno_len = std::to_string(pages.size()).length(); 3040 size_t pageno_len = std::to_string(pages.size()).length();
3031 size_t num_pages = pages.size(); 3041 size_t num_pages = pages.size();
3032 for (size_t i = 0; i < num_pages; i += QIntC::to_size(m->split_pages)) { 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,22 +2099,22 @@ bool
2099 QPDFObjectHandle::isPageObject() const 2099 QPDFObjectHandle::isPageObject() const
2100 { 2100 {
2101 // See comments in QPDFObjectHandle.hh. 2101 // See comments in QPDFObjectHandle.hh.
2102 - if (getOwningQPDF() == nullptr) { 2102 + if (!qpdf()) {
2103 return false; 2103 return false;
2104 } 2104 }
2105 // getAllPages repairs /Type when traversing the page tree. 2105 // getAllPages repairs /Type when traversing the page tree.
2106 - getOwningQPDF()->getAllPages(); 2106 + (void)qpdf()->doc().pages().all();
2107 return isDictionaryOfType("/Page"); 2107 return isDictionaryOfType("/Page");
2108 } 2108 }
2109 2109
2110 bool 2110 bool
2111 QPDFObjectHandle::isPagesObject() const 2111 QPDFObjectHandle::isPagesObject() const
2112 { 2112 {
2113 - if (getOwningQPDF() == nullptr) { 2113 + if (!qpdf()) {
2114 return false; 2114 return false;
2115 } 2115 }
2116 // getAllPages repairs /Type when traversing the page tree. 2116 // getAllPages repairs /Type when traversing the page tree.
2117 - getOwningQPDF()->getAllPages(); 2117 + (void)qpdf()->doc().pages().all();
2118 return isDictionaryOfType("/Pages"); 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,7 +27,8 @@
27 using namespace std::literals; 27 using namespace std::literals;
28 using namespace qpdf; 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 QPDFWriter::ProgressReporter::~ProgressReporter() // NOLINT (modernize-use-equals-default) 33 QPDFWriter::ProgressReporter::~ProgressReporter() // NOLINT (modernize-use-equals-default)
33 { 34 {
@@ -262,13 +263,13 @@ Pl_stack::Popper::pop() @@ -262,13 +263,13 @@ Pl_stack::Popper::pop()
262 } 263 }
263 264
264 // Writer class is restricted to QPDFWriter so that only it can call certain methods. 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 friend class QPDFWriter; 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,10 +327,9 @@ class QPDF::Doc::Writer
326 size_t 327 size_t
327 tableSize() 328 tableSize()
328 { 329 {
329 - return pdf.m->objects.tableSize(); 330 + return qpdf.m->objects.tableSize();
330 } 331 }
331 332
332 - QPDF& pdf;  
333 QPDF::Doc::Linearization& lin; 333 QPDF::Doc::Linearization& lin;
334 QPDF::Doc::Objects& objects; 334 QPDF::Doc::Objects& objects;
335 }; 335 };
@@ -352,7 +352,8 @@ class QPDFWriter::Members: QPDF::Doc::Writer @@ -352,7 +352,8 @@ class QPDFWriter::Members: QPDF::Doc::Writer
352 QPDF::Doc::Writer(pdf), 352 QPDF::Doc::Writer(pdf),
353 w(w), 353 w(w),
354 root_og( 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 pipeline_stack(pipeline) 357 pipeline_stack(pipeline)
357 { 358 {
358 } 359 }
@@ -1372,7 +1373,7 @@ QPDFWriter::Members::enqueueObject(QPDFObjectHandle object) @@ -1372,7 +1373,7 @@ QPDFWriter::Members::enqueueObject(QPDFObjectHandle object)
1372 // object to have an owning QPDF that is from another file if a direct QPDFObjectHandle from 1373 // object to have an owning QPDF that is from another file if a direct QPDFObjectHandle from
1373 // one file was insert into another file without copying. Doing that is safe even if the 1374 // one file was insert into another file without copying. Doing that is safe even if the
1374 // original QPDF gets destroyed, which just disconnects the QPDFObjectHandle from its owner. 1375 // original QPDF gets destroyed, which just disconnects the QPDFObjectHandle from its owner.
1375 - if (object.getOwningQPDF() != &pdf) { 1376 + if (object.getOwningQPDF() != &qpdf) {
1376 throw std::logic_error( 1377 throw std::logic_error(
1377 "QPDFObjectHandle from different QPDF found while writing. Use " 1378 "QPDFObjectHandle from different QPDF found while writing. Use "
1378 "QPDF::copyForeignObject to add objects from another file."); 1379 "QPDF::copyForeignObject to add objects from another file.");
@@ -1396,7 +1397,7 @@ QPDFWriter::Members::enqueueObject(QPDFObjectHandle object) @@ -1396,7 +1397,7 @@ QPDFWriter::Members::enqueueObject(QPDFObjectHandle object)
1396 // stream. Object streams always have generation 0. 1397 // stream. Object streams always have generation 0.
1397 // Detect loops by storing invalid object ID -1, which will get overwritten later. 1398 // Detect loops by storing invalid object ID -1, which will get overwritten later.
1398 o.renumber = -1; 1399 o.renumber = -1;
1399 - enqueueObject(pdf.getObject(o.object_stream, 0)); 1400 + enqueueObject(qpdf.getObject(o.object_stream, 0));
1400 } else { 1401 } else {
1401 object_queue.emplace_back(object); 1402 object_queue.emplace_back(object);
1402 o.renumber = next_objid++; 1403 o.renumber = next_objid++;
@@ -1907,7 +1908,7 @@ QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object) @@ -1907,7 +1908,7 @@ QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object)
1907 // reporting, decrement in pass 1. 1908 // reporting, decrement in pass 1.
1908 indicateProgress(true, false); 1909 indicateProgress(true, false);
1909 1910
1910 - QPDFObjectHandle obj_to_write = pdf.getObject(og); 1911 + QPDFObjectHandle obj_to_write = qpdf.getObject(og);
1911 if (obj_to_write.isStream()) { 1912 if (obj_to_write.isStream()) {
1912 // This condition occurred in a fuzz input. Ideally we should block it at parse 1913 // This condition occurred in a fuzz input. Ideally we should block it at parse
1913 // time, but it's not clear to me how to construct a case for this. 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,7 +2025,7 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde
2024 std::string 2025 std::string
2025 QPDFWriter::Members::getOriginalID1() 2026 QPDFWriter::Members::getOriginalID1()
2026 { 2027 {
2027 - QPDFObjectHandle trailer = pdf.getTrailer(); 2028 + QPDFObjectHandle trailer = qpdf.getTrailer();
2028 if (trailer.hasKey("/ID")) { 2029 if (trailer.hasKey("/ID")) {
2029 return trailer.getKey("/ID").getArrayItem(0).getStringValue(); 2030 return trailer.getKey("/ID").getArrayItem(0).getStringValue();
2030 } else { 2031 } else {
@@ -2042,7 +2043,7 @@ QPDFWriter::Members::generateID(bool encrypted) @@ -2042,7 +2043,7 @@ QPDFWriter::Members::generateID(bool encrypted)
2042 return; 2043 return;
2043 } 2044 }
2044 2045
2045 - QPDFObjectHandle trailer = pdf.getTrailer(); 2046 + QPDFObjectHandle trailer = qpdf.getTrailer();
2046 2047
2047 std::string result; 2048 std::string result;
2048 2049
@@ -2126,7 +2127,6 @@ void @@ -2126,7 +2127,6 @@ void
2126 QPDFWriter::Members::initializeSpecialStreams() 2127 QPDFWriter::Members::initializeSpecialStreams()
2127 { 2128 {
2128 // Mark all page content streams in case we are filtering or normalizing. 2129 // Mark all page content streams in case we are filtering or normalizing.
2129 - std::vector<QPDFObjectHandle> pages = pdf.getAllPages();  
2130 int num = 0; 2130 int num = 0;
2131 for (auto& page: pages) { 2131 for (auto& page: pages) {
2132 page_object_to_seq[page.getObjGen()] = ++num; 2132 page_object_to_seq[page.getObjGen()] = ++num;
@@ -2220,13 +2220,13 @@ QPDFWriter::Members::generateObjectStreams() @@ -2220,13 +2220,13 @@ QPDFWriter::Members::generateObjectStreams()
2220 ++n_per; 2220 ++n_per;
2221 } 2221 }
2222 unsigned int n = 0; 2222 unsigned int n = 0;
2223 - int cur_ostream = pdf.newIndirectNull().getObjectID(); 2223 + int cur_ostream = qpdf.newIndirectNull().getObjectID();
2224 for (auto const& item: eligible) { 2224 for (auto const& item: eligible) {
2225 if (n == n_per) { 2225 if (n == n_per) {
2226 n = 0; 2226 n = 0;
2227 // Construct a new null object as the "original" object stream. The rest of the code 2227 // Construct a new null object as the "original" object stream. The rest of the code
2228 // knows that this means we're creating the object stream from scratch. 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 auto& o = obj[item]; 2231 auto& o = obj[item];
2232 o.object_stream = cur_ostream; 2232 o.object_stream = cur_ostream;
@@ -2240,7 +2240,7 @@ QPDFWriter::Members::trimmed_trailer() @@ -2240,7 +2240,7 @@ QPDFWriter::Members::trimmed_trailer()
2240 { 2240 {
2241 // Remove keys from the trailer that necessarily have to be replaced when writing the file. 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 // Remove encryption keys 2245 // Remove encryption keys
2246 trailer.erase("/ID"); 2246 trailer.erase("/ID");
@@ -2265,8 +2265,8 @@ QPDFWriter::Members::trimmed_trailer() @@ -2265,8 +2265,8 @@ QPDFWriter::Members::trimmed_trailer()
2265 void 2265 void
2266 QPDFWriter::Members::prepareFileForWrite() 2266 QPDFWriter::Members::prepareFileForWrite()
2267 { 2267 {
2268 - pdf.fixDanglingReferences();  
2269 - auto root = pdf.getRoot(); 2268 + qpdf.fixDanglingReferences();
  2269 + auto root = qpdf.getRoot();
2270 auto oh = root.getKey("/Extensions"); 2270 auto oh = root.getKey("/Extensions");
2271 if (oh.isDictionary()) { 2271 if (oh.isDictionary()) {
2272 const bool extensions_indirect = oh.isIndirect(); 2272 const bool extensions_indirect = oh.isIndirect();
@@ -2335,7 +2335,7 @@ QPDFWriter::Members::doWriteSetup() @@ -2335,7 +2335,7 @@ QPDFWriter::Members::doWriteSetup()
2335 } 2335 }
2336 2336
2337 if (preserve_encryption) { 2337 if (preserve_encryption) {
2338 - copyEncryptionParameters(pdf); 2338 + copyEncryptionParameters(qpdf);
2339 } 2339 }
2340 2340
2341 if (!forced_pdf_version.empty()) { 2341 if (!forced_pdf_version.empty()) {
@@ -2380,7 +2380,7 @@ QPDFWriter::Members::doWriteSetup() @@ -2380,7 +2380,7 @@ QPDFWriter::Members::doWriteSetup()
2380 if (!obj.streams_empty) { 2380 if (!obj.streams_empty) {
2381 if (linearized) { 2381 if (linearized) {
2382 // Page dictionaries are not allowed to be compressed objects. 2382 // Page dictionaries are not allowed to be compressed objects.
2383 - for (auto& page: pdf.getAllPages()) { 2383 + for (auto& page: pages) {
2384 if (obj[page].object_stream > 0) { 2384 if (obj[page].object_stream > 0) {
2385 obj[page].object_stream = 0; 2385 obj[page].object_stream = 0;
2386 } 2386 }
@@ -2416,7 +2416,7 @@ QPDFWriter::Members::doWriteSetup() @@ -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 final_pdf_version = min_pdf_version; 2420 final_pdf_version = min_pdf_version;
2421 final_extension_level = min_extension_level; 2421 final_extension_level = min_extension_level;
2422 if (!forced_pdf_version.empty()) { 2422 if (!forced_pdf_version.empty()) {
@@ -2438,7 +2438,7 @@ QPDFWriter::Members::write() @@ -2438,7 +2438,7 @@ QPDFWriter::Members::write()
2438 2438
2439 // Set up progress reporting. For linearized files, we write two passes. events_expected is an 2439 // Set up progress reporting. For linearized files, we write two passes. events_expected is an
2440 // approximation, but it's good enough for progress reporting, which is mostly a guess anyway. 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 prepareFileForWrite(); 2443 prepareFileForWrite();
2444 2444
@@ -2910,14 +2910,11 @@ QPDFWriter::Members::writeLinearized() @@ -2910,14 +2910,11 @@ QPDFWriter::Members::writeLinearized()
2910 openObject(lindict_id); 2910 openObject(lindict_id);
2911 write("<<"); 2911 write("<<");
2912 if (pass == 2) { 2912 if (pass == 2) {
2913 - std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();  
2914 - int first_page_object = obj[pages.at(0)].renumber;  
2915 -  
2916 write(" /Linearized 1 /L ").write(file_size + hint_length); 2913 write(" /Linearized 1 /L ").write(file_size + hint_length);
2917 // Implementation note 121 states that a space is mandatory after this open bracket. 2914 // Implementation note 121 states that a space is mandatory after this open bracket.
2918 write(" /H [ ").write(new_obj[hint_id].xref.getOffset()).write(" "); 2915 write(" /H [ ").write(new_obj[hint_id].xref.getOffset()).write(" ");
2919 write(hint_length); 2916 write(hint_length);
2920 - write(" ] /O ").write(first_page_object); 2917 + write(" ] /O ").write(obj[pages.all().at(0)].renumber);
2921 write(" /E ").write(part6_end_offset + hint_length); 2918 write(" /E ").write(part6_end_offset + hint_length);
2922 write(" /N ").write(pages.size()); 2919 write(" /N ").write(pages.size());
2923 write(" /T ").write(space_before_zero + hint_length); 2920 write(" /T ").write(space_before_zero + hint_length);
@@ -3110,7 +3107,7 @@ void @@ -3110,7 +3107,7 @@ void
3110 QPDFWriter::Members::enqueueObjectsStandard() 3107 QPDFWriter::Members::enqueueObjectsStandard()
3111 { 3108 {
3112 if (preserve_unreferenced_objects) { 3109 if (preserve_unreferenced_objects) {
3113 - for (auto const& oh: pdf.getAllObjects()) { 3110 + for (auto const& oh: qpdf.getAllObjects()) {
3114 enqueueObject(oh); 3111 enqueueObject(oh);
3115 } 3112 }
3116 } 3113 }
@@ -3136,20 +3133,18 @@ QPDFWriter::Members::enqueueObjectsPCLm() @@ -3136,20 +3133,18 @@ QPDFWriter::Members::enqueueObjectsPCLm()
3136 std::string image_transform_content = "q /image Do Q\n"; 3133 std::string image_transform_content = "q /image Do Q\n";
3137 3134
3138 // enqueue all pages first 3135 // enqueue all pages first
3139 - std::vector<QPDFObjectHandle> all = pdf.getAllPages();  
3140 - for (auto& page: all) { 3136 + for (auto& page: pages) {
3141 // enqueue page 3137 // enqueue page
3142 enqueueObject(page); 3138 enqueueObject(page);
3143 3139
3144 // enqueue page contents stream 3140 // enqueue page contents stream
3145 - enqueueObject(page.getKey("/Contents")); 3141 + enqueueObject(page["/Contents"]);
3146 3142
3147 // enqueue all the strips for each page 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 if (!image.second.null()) { 3145 if (!image.second.null()) {
3151 enqueueObject(image.second); 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,7 +30,7 @@ using Streams = QPDF::Doc::Objects::Streams;
30 bool 30 bool
31 Streams::immediate_copy_from() const 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 class Streams::Copier final: public QPDFObjectHandle::StreamDataProvider 36 class Streams::Copier final: public QPDFObjectHandle::StreamDataProvider
@@ -83,10 +83,10 @@ class Streams::Copier final: public QPDFObjectHandle::StreamDataProvider @@ -83,10 +83,10 @@ class Streams::Copier final: public QPDFObjectHandle::StreamDataProvider
83 if (data != copied_data.end()) { 83 if (data != copied_data.end()) {
84 auto& fd = data->second; 84 auto& fd = data->second;
85 QTC::TC("qpdf", "QPDF pipe foreign encrypted stream", fd.encp->encrypted ? 0 : 1); 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 fd.encp, 87 fd.encp,
88 fd.file, 88 fd.file,
89 - streams.qpdf(), 89 + streams.qpdf,
90 fd.source_og, 90 fd.source_og,
91 fd.offset, 91 fd.offset,
92 fd.length, 92 fd.length,
@@ -128,8 +128,8 @@ class Streams::Copier final: public QPDFObjectHandle::StreamDataProvider @@ -128,8 +128,8 @@ class Streams::Copier final: public QPDFObjectHandle::StreamDataProvider
128 std::map<QPDFObjGen, Data> copied_data; 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 copier_(std::make_shared<Copier>(*this)) 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,15 +734,16 @@ QPDF::EncryptionParameters::initialize(QPDF&amp; qpdf)
734 } 734 }
735 encryption_initialized = true; 735 encryption_initialized = true;
736 736
  737 + auto& c = qpdf.m->c;
737 auto& qm = *qpdf.m; 738 auto& qm = *qpdf.m;
738 auto& trailer = qm.trailer; 739 auto& trailer = qm.trailer;
739 auto& file = qm.file; 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 auto throw_damaged_pdf = [&qpdf](std::string const& msg) { 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 auto unsupported = [&file](std::string const& msg) -> QPDFExc { 748 auto unsupported = [&file](std::string const& msg) -> QPDFExc {
748 return { 749 return {
@@ -770,14 +771,14 @@ QPDF::EncryptionParameters::initialize(QPDF&amp; qpdf) @@ -770,14 +771,14 @@ QPDF::EncryptionParameters::initialize(QPDF&amp; qpdf)
770 if (id_obj.size() != 2 || !id_obj.getArrayItem(0).isString()) { 771 if (id_obj.size() != 2 || !id_obj.getArrayItem(0).isString()) {
771 // Treating a missing ID as the empty string enables qpdf to decrypt some invalid encrypted 772 // Treating a missing ID as the empty string enables qpdf to decrypt some invalid encrypted
772 // files with no /ID that poppler can read but Adobe Reader can't. 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 } else { 775 } else {
775 id1 = id_obj.getArrayItem(0).getStringValue(); 776 id1 = id_obj.getArrayItem(0).getStringValue();
776 } 777 }
777 778
778 auto encryption_dict = trailer.getKey("/Encrypt"); 779 auto encryption_dict = trailer.getKey("/Encrypt");
779 if (!encryption_dict.isDictionary()) { 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 if (Name(encryption_dict["/Filter"]) != "/Standard") { 784 if (Name(encryption_dict["/Filter"]) != "/Standard") {
@@ -984,7 +985,7 @@ QPDF::decryptString(std::string&amp; str, QPDFObjGen og) @@ -984,7 +985,7 @@ QPDF::decryptString(std::string&amp; str, QPDFObjGen og)
984 break; 985 break;
985 986
986 default: 987 default:
987 - warn(damagedPDF( 988 + warn(m->c.damagedPDF(
988 "unknown encryption filter for strings (check /StrF in " 989 "unknown encryption filter for strings (check /StrF in "
989 "/Encrypt dictionary); strings may be decrypted improperly")); 990 "/Encrypt dictionary); strings may be decrypted improperly"));
990 // To avoid repeated warnings, reset cf_string. Assume we'd want to use AES if V == 4. 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,7 +1018,8 @@ QPDF::decryptString(std::string&amp; str, QPDFObjGen og)
1017 } catch (QPDFExc&) { 1018 } catch (QPDFExc&) {
1018 throw; 1019 throw;
1019 } catch (std::runtime_error& e) { 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,110 +460,111 @@ QPDF::JSONReactor::dictionaryItem(std::string const&amp; key, JSON const&amp; value)
460 next_state = st_ignore; 460 next_state = st_ignore;
461 auto state = stack.back().state; 461 auto state = stack.back().state;
462 if (state == st_ignore) { 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 if (key == "qpdf") { 466 if (key == "qpdf") {
467 - this->saw_qpdf = true; 467 + saw_qpdf = true;
468 if (!value.isArray()) { 468 if (!value.isArray()) {
469 - QTC::TC("qpdf", "QPDF_json qpdf not array");  
470 error(value.getStart(), "\"qpdf\" must be an array"); 469 error(value.getStart(), "\"qpdf\" must be an array");
471 } else { 470 } else {
472 next_state = st_qpdf; 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 if (key == "pdfversion") { 479 if (key == "pdfversion") {
480 - this->saw_pdf_version = true; 480 + saw_pdf_version = true;
481 std::string v; 481 std::string v;
482 - bool okay = false;  
483 if (value.getString(v)) { 482 if (value.getString(v)) {
484 std::string version; 483 std::string version;
485 char const* p = v.c_str(); 484 char const* p = v.c_str();
486 if (QPDF::validatePDFVersion(p, version) && (*p == '\0')) { 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 std::string v; 495 std::string v;
498 - bool okay = false;  
499 if (value.getNumber(v)) { 496 if (value.getNumber(v)) {
500 std::string version; 497 std::string version;
501 if (QUtil::string_to_int(v.c_str()) == 2) { 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 bool v; 506 bool v;
511 if (value.getBool(v)) { 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 bool v; 517 bool v;
521 if (value.getBool(v)) { 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 if (key == "trailer") { 533 if (key == "trailer") {
538 - this->saw_trailer = true;  
539 - this->cur_object = "trailer"; 534 + saw_trailer = true;
  535 + cur_object = "trailer";
540 setNextStateIfDictionary(key, value, st_trailer); 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 if (setNextStateIfDictionary(key, value, st_object_top)) { 544 if (setNextStateIfDictionary(key, value, st_object_top)) {
544 next_obj = objects.getObjectForJSON(obj, gen); 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 auto& tos = stack.back(); 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 if (key == "value") { 557 if (key == "value") {
559 // Don't use setNextStateIfDictionary since this can have any type. 558 // Don't use setNextStateIfDictionary since this can have any type.
560 - this->saw_value = true; 559 + saw_value = true;
561 replaceObject(makeObject(value), value); 560 replaceObject(makeObject(value), value);
562 next_state = st_object; 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 if (setNextStateIfDictionary(key, value, st_stream)) { 566 if (setNextStateIfDictionary(key, value, st_stream)) {
566 - this->this_stream_needs_data = false; 567 + this_stream_needs_data = false;
567 if (tos.object.isStream()) { 568 if (tos.object.isStream()) {
568 QTC::TC("qpdf", "QPDF_json updating existing stream"); 569 QTC::TC("qpdf", "QPDF_json updating existing stream");
569 } else { 570 } else {
@@ -574,97 +575,87 @@ QPDF::JSONReactor::dictionaryItem(std::string const&amp; key, JSON const&amp; value) @@ -574,97 +575,87 @@ QPDF::JSONReactor::dictionaryItem(std::string const&amp; key, JSON const&amp; value)
574 value); 575 value);
575 } 576 }
576 next_obj = tos.object; 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 if (key == "value") { 586 if (key == "value") {
587 - this->saw_value = true; 587 + saw_value = true;
588 // The trailer must be a dictionary, so we can use setNextStateIfDictionary. 588 // The trailer must be a dictionary, so we can use setNextStateIfDictionary.
589 if (setNextStateIfDictionary("trailer.value", value, st_object)) { 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 // Don't need to set saw_stream here since there's already an error. 596 // Don't need to set saw_stream here since there's already an error.
595 - QTC::TC("qpdf", "QPDF_json trailer stream");  
596 error(value.getStart(), "the trailer may not be a stream"); 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 auto& tos = stack.back(); 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 if (key == "dict") { 607 if (key == "dict") {
610 - this->saw_dict = true; 608 + saw_dict = true;
611 if (setNextStateIfDictionary("stream.dict", value, st_object)) { 609 if (setNextStateIfDictionary("stream.dict", value, st_object)) {
612 tos.object.replaceDict(makeObject(value)); 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 std::string v; 617 std::string v;
620 if (!value.getString(v)) { 618 if (!value.getString(v)) {
621 - QTC::TC("qpdf", "QPDF_json stream data not string");  
622 error(value.getStart(), "\"stream.data\" must be a string"); 619 error(value.getStart(), "\"stream.data\" must be a string");
623 tos.object.replaceStreamData("", {}, {}); 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 std::string filename; 632 std::string filename;
636 if (!value.getString(filename)) { 633 if (!value.getString(filename)) {
637 - QTC::TC("qpdf", "QPDF_json stream datafile not string");  
638 error( 634 error(
639 value.getStart(), 635 value.getStart(),
640 "\"stream.datafile\" must be a string containing a file name"); 636 "\"stream.datafile\" must be a string containing a file name");
641 tos.object.replaceStreamData("", {}, {}); 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 return true; 659 return true;
669 } 660 }
670 661
libqpdf/QPDF_linearization.cc
@@ -69,11 +69,298 @@ load_vector_vector( @@ -69,11 +69,298 @@ load_vector_vector(
69 bit_stream.skipToNextByte(); 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 void 359 void
73 Lin::linearizationWarning(std::string_view msg) 360 Lin::linearizationWarning(std::string_view msg)
74 { 361 {
75 m->linearization_warnings = true; 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 bool 366 bool
@@ -166,19 +453,19 @@ Lin::readLinearizationData() @@ -166,19 +453,19 @@ Lin::readLinearizationData()
166 Integer P = P_oh; // first page number 453 Integer P = P_oh; // first page number
167 QTC::TC("qpdf", "QPDF P absent in lindict", P ? 0 : 1); 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 !(H && O && E && N && T && (P || P_oh.null())), 457 !(H && O && E && N && T && (P || P_oh.null())),
171 "some keys in linearization dictionary are of the wrong type", 458 "some keys in linearization dictionary are of the wrong type",
172 "linearization dictionary" // 459 "linearization dictionary" //
173 ); 460 );
174 461
175 - qpdf.no_ci_stop_if( 462 + no_ci_stop_if(
176 !(H_size == 2 || H_size == 4), 463 !(H_size == 2 || H_size == 4),
177 "H has the wrong number of items", 464 "H has the wrong number of items",
178 "linearization dictionary" // 465 "linearization dictionary" //
179 ); 466 );
180 467
181 - qpdf.no_ci_stop_if( 468 + no_ci_stop_if(
182 !(H_0 && H_1 && (H_size == 2 || (H_2 && H_3))), 469 !(H_0 && H_1 && (H_size == 2 || (H_2 && H_3))),
183 "some H items are of the wrong type", 470 "some H items are of the wrong type",
184 "linearization dictionary" // 471 "linearization dictionary" //
@@ -188,8 +475,8 @@ Lin::readLinearizationData() @@ -188,8 +475,8 @@ Lin::readLinearizationData()
188 475
189 // Various places in the code use linp.npages, which is initialized from N, to pre-allocate 476 // Various places in the code use linp.npages, which is initialized from N, to pre-allocate
190 // memory, so make sure it's accurate and bail right now if it's not. 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 "/N does not match number of pages", 480 "/N does not match number of pages",
194 "linearization dictionary" // 481 "linearization dictionary" //
195 ); 482 );
@@ -234,13 +521,12 @@ Lin::readLinearizationData() @@ -234,13 +521,12 @@ Lin::readLinearizationData()
234 521
235 size_t HSi = HS; 522 size_t HSi = HS;
236 if (HSi < 0 || HSi >= h_size) { 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 readHSharedObject(BitStream(h_buf + HSi, h_size - HSi)); 526 readHSharedObject(BitStream(h_buf + HSi, h_size - HSi));
241 527
242 if (HO) { 528 if (HO) {
243 - qpdf.no_ci_stop_if( 529 + no_ci_stop_if(
244 HO < 0 || HO >= h_size, 530 HO < 0 || HO >= h_size,
245 "/O (outline) offset is out of bounds", 531 "/O (outline) offset is out of bounds",
246 "linearization dictionary" // 532 "linearization dictionary" //
@@ -257,7 +543,7 @@ Lin::readHintStream(Pipeline&amp; pl, qpdf_offset_t offset, size_t length) @@ -257,7 +543,7 @@ Lin::readHintStream(Pipeline&amp; pl, qpdf_offset_t offset, size_t length)
257 ObjCache& oc = m->obj_cache[H]; 543 ObjCache& oc = m->obj_cache[H];
258 qpdf_offset_t min_end_offset = oc.end_before_space; 544 qpdf_offset_t min_end_offset = oc.end_before_space;
259 qpdf_offset_t max_end_offset = oc.end_after_space; 545 qpdf_offset_t max_end_offset = oc.end_after_space;
260 - qpdf.no_ci_stop_if( 546 + no_ci_stop_if(
261 !H.isStream(), "hint table is not a stream", "linearization dictionary" // 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,7 +561,7 @@ Lin::readHintStream(Pipeline&amp; pl, qpdf_offset_t offset, size_t length)
275 QTC::TC("qpdf", "QPDF hint table length direct"); 561 QTC::TC("qpdf", "QPDF hint table length direct");
276 } 562 }
277 qpdf_offset_t computed_end = offset + toO(length); 563 qpdf_offset_t computed_end = offset + toO(length);
278 - qpdf.no_ci_stop_if( 564 + no_ci_stop_if(
279 computed_end < min_end_offset || computed_end > max_end_offset, 565 computed_end < min_end_offset || computed_end > max_end_offset,
280 "hint table length mismatch (expected = " + std::to_string(computed_end) + "; actual = " + 566 "hint table length mismatch (expected = " + std::to_string(computed_end) + "; actual = " +
281 std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset) + ")", 567 std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset) + ")",
@@ -391,20 +677,20 @@ Lin::checkLinearizationInternal() @@ -391,20 +677,20 @@ Lin::checkLinearizationInternal()
391 // L: file size in bytes -- checked by isLinearized 677 // L: file size in bytes -- checked by isLinearized
392 678
393 // O: object number of first page 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 linearizationWarning("first page object (/O) mismatch"); 682 linearizationWarning("first page object (/O) mismatch");
397 } 683 }
398 684
399 // N: number of pages 685 // N: number of pages
400 - size_t npages = pages.size(); 686 + size_t npages = all_pages.size();
401 if (std::cmp_not_equal(p.npages, npages)) { 687 if (std::cmp_not_equal(p.npages, npages)) {
402 // Not tested in the test suite 688 // Not tested in the test suite
403 linearizationWarning("page count (/N) mismatch"); 689 linearizationWarning("page count (/N) mismatch");
404 } 690 }
405 691
406 int i = 0; 692 int i = 0;
407 - for (auto const& page: pages) { 693 + for (auto const& page: all_pages) {
408 if (m->xref_table[page].getType() == 2) { 694 if (m->xref_table[page].getType() == 2) {
409 linearizationWarning( 695 linearizationWarning(
410 "page dictionary for page " + std::to_string(i) + " is compressed"); 696 "page dictionary for page " + std::to_string(i) + " is compressed");
@@ -464,7 +750,7 @@ Lin::checkLinearizationInternal() @@ -464,7 +750,7 @@ Lin::checkLinearizationInternal()
464 // are present. In that case, it would probably agree with pdlin. As of this writing, the test 750 // are present. In that case, it would probably agree with pdlin. As of this writing, the test
465 // suite doesn't contain any files with threads. 751 // suite doesn't contain any files with threads.
466 752
467 - qpdf.no_ci_stop_if( 753 + no_ci_stop_if(
468 m->part6.empty(), "linearization part 6 unexpectedly empty" // 754 m->part6.empty(), "linearization part 6 unexpectedly empty" //
469 ); 755 );
470 qpdf_offset_t min_E = -1; 756 qpdf_offset_t min_E = -1;
@@ -486,22 +772,22 @@ Lin::checkLinearizationInternal() @@ -486,22 +772,22 @@ Lin::checkLinearizationInternal()
486 // Check hint tables 772 // Check hint tables
487 773
488 std::map<int, int> shared_idx_to_obj; 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 checkHOutlines(); 777 checkHOutlines();
492 } 778 }
493 779
494 qpdf_offset_t 780 qpdf_offset_t
495 Lin::maxEnd(ObjUser const& ou) 781 Lin::maxEnd(ObjUser const& ou)
496 { 782 {
497 - qpdf.no_ci_stop_if( 783 + no_ci_stop_if(
498 !m->obj_user_to_objects.contains(ou), 784 !m->obj_user_to_objects.contains(ou),
499 "no entry in object user table for requested object user" // 785 "no entry in object user table for requested object user" //
500 ); 786 );
501 787
502 qpdf_offset_t end = 0; 788 qpdf_offset_t end = 0;
503 for (auto const& og: m->obj_user_to_objects[ou]) { 789 for (auto const& og: m->obj_user_to_objects[ou]) {
504 - qpdf.no_ci_stop_if( 790 + no_ci_stop_if(
505 !m->obj_cache.contains(og), "unknown object referenced in object user table" // 791 !m->obj_cache.contains(og), "unknown object referenced in object user table" //
506 ); 792 );
507 end = std::max(end, m->obj_cache[og].end_after_space); 793 end = std::max(end, m->obj_cache[og].end_after_space);
@@ -517,7 +803,7 @@ Lin::getLinearizationOffset(QPDFObjGen og) @@ -517,7 +803,7 @@ Lin::getLinearizationOffset(QPDFObjGen og)
517 if (typ == 1) { 803 if (typ == 1) {
518 return entry.getOffset(); 804 return entry.getOffset();
519 } 805 }
520 - qpdf.no_ci_stop_if( 806 + no_ci_stop_if(
521 typ != 2, "getLinearizationOffset called for xref entry not of type 1 or 2" // 807 typ != 2, "getLinearizationOffset called for xref entry not of type 1 or 2" //
522 ); 808 );
523 // For compressed objects, return the offset of the object stream that contains them. 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,7 +837,7 @@ Lin::lengthNextN(int first_object, int n)
551 for (int i = 0; i < n; ++i) { 837 for (int i = 0; i < n; ++i) {
552 QPDFObjGen og(first_object + i, 0); 838 QPDFObjGen og(first_object + i, 0);
553 if (m->xref_table.contains(og)) { 839 if (m->xref_table.contains(og)) {
554 - qpdf.no_ci_stop_if( 840 + no_ci_stop_if(
555 !m->obj_cache.contains(og), 841 !m->obj_cache.contains(og),
556 "found unknown object while calculating length for linearization data" // 842 "found unknown object while calculating length for linearization data" //
557 ); 843 );
@@ -585,7 +871,7 @@ Lin::checkHPageOffset( @@ -585,7 +871,7 @@ Lin::checkHPageOffset(
585 qpdf_offset_t table_offset = adjusted_offset(m->page_offset_hints.first_page_offset); 871 qpdf_offset_t table_offset = adjusted_offset(m->page_offset_hints.first_page_offset);
586 QPDFObjGen first_page_og(pages.at(0).getObjGen()); 872 QPDFObjGen first_page_og(pages.at(0).getObjGen());
587 if (!m->xref_table.contains(first_page_og)) { 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 qpdf_offset_t offset = getLinearizationOffset(first_page_og); 876 qpdf_offset_t offset = getLinearizationOffset(first_page_og);
591 if (table_offset != offset) { 877 if (table_offset != offset) {
@@ -596,7 +882,7 @@ Lin::checkHPageOffset( @@ -596,7 +882,7 @@ Lin::checkHPageOffset(
596 QPDFObjGen page_og(pages.at(pageno).getObjGen()); 882 QPDFObjGen page_og(pages.at(pageno).getObjGen());
597 int first_object = page_og.getObj(); 883 int first_object = page_og.getObj();
598 if (!m->xref_table.contains(page_og)) { 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 offset = getLinearizationOffset(page_og); 887 offset = getLinearizationOffset(page_og);
602 888
@@ -636,7 +922,7 @@ Lin::checkHPageOffset( @@ -636,7 +922,7 @@ Lin::checkHPageOffset(
636 922
637 for (size_t i = 0; i < toS(he.nshared_objects); ++i) { 923 for (size_t i = 0; i < toS(he.nshared_objects); ++i) {
638 int idx = he.shared_identifiers.at(i); 924 int idx = he.shared_identifiers.at(i);
639 - qpdf.no_ci_stop_if( 925 + no_ci_stop_if(
640 !shared_idx_to_obj.contains(idx), 926 !shared_idx_to_obj.contains(idx),
641 "unable to get object for item in shared objects hint table"); 927 "unable to get object for item in shared objects hint table");
642 928
@@ -645,7 +931,7 @@ Lin::checkHPageOffset( @@ -645,7 +931,7 @@ Lin::checkHPageOffset(
645 931
646 for (size_t i = 0; i < toS(ce.nshared_objects); ++i) { 932 for (size_t i = 0; i < toS(ce.nshared_objects); ++i) {
647 int idx = ce.shared_identifiers.at(i); 933 int idx = ce.shared_identifiers.at(i);
648 - qpdf.no_ci_stop_if( 934 + no_ci_stop_if(
649 idx >= m->c_shared_object_data.nshared_total, 935 idx >= m->c_shared_object_data.nshared_total,
650 "index out of bounds for shared object hint table" // 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,7 +1004,7 @@ Lin::checkHSharedObject(std::vector&lt;QPDFObjectHandle&gt; const&amp; pages, std::map&lt;int
718 1004
719 QPDFObjGen og(cur_object, 0); 1005 QPDFObjGen og(cur_object, 0);
720 if (!m->xref_table.contains(og)) { 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 qpdf_offset_t offset = getLinearizationOffset(og); 1009 qpdf_offset_t offset = getLinearizationOffset(og);
724 qpdf_offset_t h_offset = adjusted_offset(so.first_shared_offset); 1010 qpdf_offset_t h_offset = adjusted_offset(so.first_shared_offset);
@@ -768,7 +1054,7 @@ Lin::checkHOutlines() @@ -768,7 +1054,7 @@ Lin::checkHOutlines()
768 return; 1054 return;
769 } 1055 }
770 QPDFObjGen og(outlines.getObjGen()); 1056 QPDFObjGen og(outlines.getObjGen());
771 - qpdf.no_ci_stop_if( 1057 + no_ci_stop_if(
772 !m->xref_table.contains(og), "unknown object in outlines hint table" // 1058 !m->xref_table.contains(og), "unknown object in outlines hint table" //
773 ); 1059 );
774 qpdf_offset_t offset = getLinearizationOffset(og); 1060 qpdf_offset_t offset = getLinearizationOffset(og);
@@ -1105,12 +1391,12 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data) @@ -1105,12 +1391,12 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1105 1391
1106 // We seem to traverse the page tree a lot in this code, but we can address this for a future 1392 // We seem to traverse the page tree a lot in this code, but we can address this for a future
1107 // code optimization if necessary. Premature optimization is the root of all evil. 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 { // local scope 1395 { // local scope
1110 // Map all page objects to the containing object stream. This should be a no-op in a 1396 // Map all page objects to the containing object stream. This should be a no-op in a
1111 // properly linearized file. 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 size_t npages = pages.size(); 1402 size_t npages = pages.size();
@@ -1128,7 +1414,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data) @@ -1128,7 +1414,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1128 1414
1129 // Part 4: open document objects. We don't care about the order. 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 lc_root.size() != 1, "found other than one root while calculating linearization data" // 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,15 +1428,15 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1142 // any option to set this and also disregards /OpenAction. We will do the same. 1428 // any option to set this and also disregards /OpenAction. We will do the same.
1143 1429
1144 // First, place the actual first page object itself. 1430 // First, place the actual first page object itself.
1145 - qpdf.no_ci_stop_if( 1431 + no_ci_stop_if(
1146 pages.empty(), "no pages found while calculating linearization data" // 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 !lc_first_page_private.erase(first_page_og), "unable to linearize first page" // 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 // The PDF spec "recommends" an order for the rest of the objects, but we are going to disregard 1441 // The PDF spec "recommends" an order for the rest of the objects, but we are going to disregard
1156 // it except to the extent that it groups private and shared objects contiguously for the sake 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,13 +1467,13 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1181 for (size_t i = 1; i < npages; ++i) { 1467 for (size_t i = 1; i < npages; ++i) {
1182 // Place this page's page object 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 !lc_other_page_private.erase(page_og), 1472 !lc_other_page_private.erase(page_og),
1187 "unable to linearize page " + std::to_string(i) // 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 // Place all non-shared objects referenced by this page, updating the page object count for 1478 // Place all non-shared objects referenced by this page, updating the page object count for
1193 // the hint table. 1479 // the hint table.
@@ -1195,7 +1481,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data) @@ -1195,7 +1481,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1195 m->c_page_offset_data.entries.at(i).nobjects = 1; 1481 m->c_page_offset_data.entries.at(i).nobjects = 1;
1196 1482
1197 ObjUser ou(ObjUser::ou_page, i); 1483 ObjUser ou(ObjUser::ou_page, i);
1198 - qpdf.no_ci_stop_if( 1484 + no_ci_stop_if(
1199 !m->obj_user_to_objects.contains(ou), 1485 !m->obj_user_to_objects.contains(ou),
1200 "found unreferenced page while calculating linearization data" // 1486 "found unreferenced page while calculating linearization data" //
1201 ); 1487 );
@@ -1231,7 +1517,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data) @@ -1231,7 +1517,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1231 // Place the pages tree. 1517 // Place the pages tree.
1232 std::set<QPDFObjGen> pages_ogs = 1518 std::set<QPDFObjGen> pages_ogs =
1233 m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")]; 1519 m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")];
1234 - qpdf.no_ci_stop_if( 1520 + no_ci_stop_if(
1235 pages_ogs.empty(), "found empty pages tree while calculating linearization data" // 1521 pages_ogs.empty(), "found empty pages tree while calculating linearization data" //
1236 ); 1522 );
1237 for (auto const& og: pages_ogs) { 1523 for (auto const& og: pages_ogs) {
@@ -1243,7 +1529,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data) @@ -1243,7 +1529,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1243 // Place private thumbnail images in page order. Slightly more information would be required if 1529 // Place private thumbnail images in page order. Slightly more information would be required if
1244 // we were going to bother with thumbnail hint tables. 1530 // we were going to bother with thumbnail hint tables.
1245 for (size_t i = 0; i < npages; ++i) { 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 thumb = getUncompressedObject(thumb, object_stream_data); 1533 thumb = getUncompressedObject(thumb, object_stream_data);
1248 QPDFObjGen thumb_og(thumb.getObjGen()); 1534 QPDFObjGen thumb_og(thumb.getObjGen());
1249 // Output the thumbnail itself 1535 // Output the thumbnail itself
@@ -1288,7 +1574,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data) @@ -1288,7 +1574,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1288 size_t num_placed = 1574 size_t num_placed =
1289 m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size(); 1575 m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size();
1290 size_t num_wanted = m->object_to_obj_users.size(); 1576 size_t num_wanted = m->object_to_obj_users.size();
1291 - qpdf.no_ci_stop_if( 1577 + no_ci_stop_if(
1292 // This can happen with damaged files, e.g. if the root is part of the the pages tree. 1578 // This can happen with damaged files, e.g. if the root is part of the the pages tree.
1293 num_placed != num_wanted, 1579 num_placed != num_wanted,
1294 "QPDF::calculateLinearizationData: wrong number of objects placed (num_placed = " + 1580 "QPDF::calculateLinearizationData: wrong number of objects placed (num_placed = " +
@@ -1326,7 +1612,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data) @@ -1326,7 +1612,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1326 shared.emplace_back(obj); 1612 shared.emplace_back(obj);
1327 } 1613 }
1328 } 1614 }
1329 - qpdf.no_ci_stop_if( 1615 + no_ci_stop_if(
1330 std::cmp_not_equal( 1616 std::cmp_not_equal(
1331 m->c_shared_object_data.nshared_total, m->c_shared_object_data.entries.size()), 1617 m->c_shared_object_data.nshared_total, m->c_shared_object_data.entries.size()),
1332 "shared object hint table has wrong number of entries" // 1618 "shared object hint table has wrong number of entries" //
@@ -1337,7 +1623,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data) @@ -1337,7 +1623,7 @@ Lin::calculateLinearizationData(T const&amp; object_stream_data)
1337 for (size_t i = 1; i < npages; ++i) { 1623 for (size_t i = 1; i < npages; ++i) {
1338 CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i); 1624 CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i);
1339 ObjUser ou(ObjUser::ou_page, i); 1625 ObjUser ou(ObjUser::ou_page, i);
1340 - qpdf.no_ci_stop_if( 1626 + no_ci_stop_if(
1341 !m->obj_user_to_objects.contains(ou), 1627 !m->obj_user_to_objects.contains(ou),
1342 "found unreferenced page while calculating linearization data" // 1628 "found unreferenced page while calculating linearization data" //
1343 ); 1629 );
@@ -1420,12 +1706,12 @@ Lin::outputLengthNextN( @@ -1420,12 +1706,12 @@ Lin::outputLengthNextN(
1420 1706
1421 int first = obj[in_object].renumber; 1707 int first = obj[in_object].renumber;
1422 int last = first + n; 1708 int last = first + n;
1423 - qpdf.no_ci_stop_if( 1709 + no_ci_stop_if(
1424 first <= 0, "found object that is not renumbered while writing linearization data"); 1710 first <= 0, "found object that is not renumbered while writing linearization data");
1425 qpdf_offset_t length = 0; 1711 qpdf_offset_t length = 0;
1426 for (int i = first; i < last; ++i) { 1712 for (int i = first; i < last; ++i) {
1427 auto l = new_obj[i].length; 1713 auto l = new_obj[i].length;
1428 - qpdf.no_ci_stop_if( 1714 + no_ci_stop_if(
1429 l == 0, "found item with unknown length while writing linearization data" // 1715 l == 0, "found item with unknown length while writing linearization data" //
1430 ); 1716 );
1431 length += l; 1717 length += l;
@@ -1440,8 +1726,8 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob @@ -1440,8 +1726,8 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob
1440 1726
1441 // We are purposely leaving some values set to their initial zero values. 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 CHPageOffset& cph = m->c_page_offset_data; 1731 CHPageOffset& cph = m->c_page_offset_data;
1446 std::vector<CHPageOffsetEntry>& cphe = cph.entries; 1732 std::vector<CHPageOffsetEntry>& cphe = cph.entries;
1447 1733
@@ -1467,7 +1753,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob @@ -1467,7 +1753,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob
1467 // assignments. 1753 // assignments.
1468 1754
1469 int nobjects = cphe.at(i).nobjects; 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 int nshared = cphe.at(i).nshared_objects; 1757 int nshared = cphe.at(i).nshared_objects;
1472 1758
1473 min_nobjects = std::min(min_nobjects, nobjects); 1759 min_nobjects = std::min(min_nobjects, nobjects);
@@ -1483,7 +1769,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob @@ -1483,7 +1769,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob
1483 } 1769 }
1484 1770
1485 ph.min_nobjects = min_nobjects; 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 ph.nbits_delta_nobjects = nbits(max_nobjects - min_nobjects); 1773 ph.nbits_delta_nobjects = nbits(max_nobjects - min_nobjects);
1488 ph.min_page_length = min_length; 1774 ph.min_page_length = min_length;
1489 ph.nbits_delta_page_length = nbits(max_length - min_length); 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,7 +1788,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::Ob
1502 for (auto& phe_i: phe) { 1788 for (auto& phe_i: phe) {
1503 // Adjust delta entries 1789 // Adjust delta entries
1504 if (phe_i.delta_nobjects < min_nobjects || phe_i.delta_page_length < min_length) { 1790 if (phe_i.delta_nobjects < min_nobjects || phe_i.delta_page_length < min_length) {
1505 - qpdf.stopOnError( 1791 + stopOnError(
1506 "found too small delta nobjects or delta page length while writing " 1792 "found too small delta nobjects or delta page length while writing "
1507 "linearization data"); 1793 "linearization data");
1508 } 1794 }
@@ -1537,7 +1823,7 @@ Lin::calculateHSharedObject(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter:: @@ -1537,7 +1823,7 @@ Lin::calculateHSharedObject(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::
1537 soe.emplace_back(); 1823 soe.emplace_back();
1538 soe.at(i).delta_group_length = length; 1824 soe.at(i).delta_group_length = length;
1539 } 1825 }
1540 - qpdf.no_ci_stop_if( 1826 + no_ci_stop_if(
1541 soe.size() != toS(cso.nshared_total), "soe has wrong size after initialization" // 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,7 +1839,7 @@ Lin::calculateHSharedObject(QPDFWriter::NewObjTable const&amp; new_obj, QPDFWriter::
1553 1839
1554 for (size_t i = 0; i < toS(cso.nshared_total); ++i) { 1840 for (size_t i = 0; i < toS(cso.nshared_total); ++i) {
1555 // Adjust deltas 1841 // Adjust deltas
1556 - qpdf.no_ci_stop_if( 1842 + no_ci_stop_if(
1557 soe.at(i).delta_group_length < min_length, 1843 soe.at(i).delta_group_length < min_length,
1558 "found too small group length while writing linearization data" // 1844 "found too small group length while writing linearization data" //
1559 ); 1845 );
@@ -1632,7 +1918,7 @@ Lin::writeHPageOffset(BitWriter&amp; w) @@ -1632,7 +1918,7 @@ Lin::writeHPageOffset(BitWriter&amp; w)
1632 w.writeBitsInt(t.nbits_shared_numerator, 16); // 12 1918 w.writeBitsInt(t.nbits_shared_numerator, 16); // 12
1633 w.writeBitsInt(t.shared_denominator, 16); // 13 1919 w.writeBitsInt(t.shared_denominator, 16); // 13
1634 1920
1635 - int nitems = toI(qpdf.getAllPages().size()); 1921 + int nitems = toI(pages.size());
1636 std::vector<HPageOffsetEntry>& entries = t.entries; 1922 std::vector<HPageOffsetEntry>& entries = t.entries;
1637 1923
1638 write_vector_int(w, nitems, entries, t.nbits_delta_nobjects, &HPageOffsetEntry::delta_nobjects); 1924 write_vector_int(w, nitems, entries, t.nbits_delta_nobjects, &HPageOffsetEntry::delta_nobjects);
@@ -1687,7 +1973,7 @@ Lin::writeHSharedObject(BitWriter&amp; w) @@ -1687,7 +1973,7 @@ Lin::writeHSharedObject(BitWriter&amp; w)
1687 for (size_t i = 0; i < toS(nitems); ++i) { 1973 for (size_t i = 0; i < toS(nitems); ++i) {
1688 // If signature were present, we'd have to write a 128-bit hash. 1974 // If signature were present, we'd have to write a 128-bit hash.
1689 if (entries.at(i).signature_present != 0) { 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 write_vector_int(w, nitems, entries, t.nbits_nobjects, &HSharedObjectEntry::nobjects_minus_one); 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,7 +123,7 @@ Objects::parse(char const* password)
123 // Find the header anywhere in the first 1024 bytes of the file. 123 // Find the header anywhere in the first 1024 bytes of the file.
124 PatternFinder hf(qpdf, &QPDF::findHeader); 124 PatternFinder hf(qpdf, &QPDF::findHeader);
125 if (!m->file->findFirst("%PDF-", 0, 1024, hf)) { 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 // QPDFWriter writes files that usually require at least version 1.2 for /FlateDecode 127 // QPDFWriter writes files that usually require at least version 1.2 for /FlateDecode
128 m->pdf_version = "1.2"; 128 m->pdf_version = "1.2";
129 } 129 }
@@ -147,14 +147,14 @@ Objects::parse(char const* password) @@ -147,14 +147,14 @@ Objects::parse(char const* password)
147 147
148 try { 148 try {
149 if (xref_offset == 0) { 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 try { 152 try {
153 read_xref(xref_offset); 153 read_xref(xref_offset);
154 } catch (QPDFExc&) { 154 } catch (QPDFExc&) {
155 throw; 155 throw;
156 } catch (std::exception& e) { 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 } catch (QPDFExc& e) { 159 } catch (QPDFExc& e) {
160 if (m->attempt_recovery) { 160 if (m->attempt_recovery) {
@@ -168,7 +168,7 @@ Objects::parse(char const* password) @@ -168,7 +168,7 @@ Objects::parse(char const* password)
168 m->parsed = true; 168 m->parsed = true;
169 if (!m->xref_table.empty() && !qpdf.getRoot().getKey("/Pages").isDictionary()) { 169 if (!m->xref_table.empty() && !qpdf.getRoot().getKey("/Pages").isDictionary()) {
170 // QPDFs created from JSON have an empty xref table and no root object yet. 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,8 +208,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
208 const auto max_warnings = m->warnings.size() + 1000U; 208 const auto max_warnings = m->warnings.size() + 1000U;
209 auto check_warnings = [this, max_warnings]() { 209 auto check_warnings = [this, max_warnings]() {
210 if (m->warnings.size() > max_warnings) { 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,9 +216,9 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
217 // We may find more objects, which may contain dangling references. 216 // We may find more objects, which may contain dangling references.
218 m->fixed_dangling_refs = false; 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 // Delete all references to type 1 (uncompressed) objects 223 // Delete all references to type 1 (uncompressed) objects
225 std::vector<QPDFObjGen> to_delete; 224 std::vector<QPDFObjGen> to_delete;
@@ -253,7 +252,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref) @@ -253,7 +252,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
253 if (obj <= m->xref_table_max_id) { 252 if (obj <= m->xref_table_max_id) {
254 found_objects.emplace_back(obj, gen, token_start); 253 found_objects.emplace_back(obj, gen, token_start);
255 } else { 254 } else {
256 - qpdf.warn(qpdf.damagedPDF( 255 + warn(damagedPDF(
257 "", -1, "ignoring object with impossibly large id " + std::to_string(obj))); 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,7 +277,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
278 277
279 if (qpdf.getRoot().getKey("/Pages").isDictionary()) { 278 if (qpdf.getRoot().getKey("/Pages").isDictionary()) {
280 QTC::TC("qpdf", "QPDF startxref more than 1024 before end"); 279 QTC::TC("qpdf", "QPDF startxref more than 1024 before end");
281 - qpdf.warn(qpdf.damagedPDF( 280 + warn(damagedPDF(
282 "", -1, "startxref was more than 1024 bytes before end of file")); 281 "", -1, "startxref was more than 1024 bytes before end of file"));
283 qpdf.initializeEncryption(); 282 qpdf.initializeEncryption();
284 m->parsed = true; 283 m->parsed = true;
@@ -313,7 +312,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref) @@ -313,7 +312,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
313 m->trailer = t; 312 m->trailer = t;
314 break; 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 check_warnings(); 317 check_warnings();
319 } 318 }
@@ -347,7 +346,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref) @@ -347,7 +346,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
347 try { 346 try {
348 read_xref(max_offset, true); 347 read_xref(max_offset, true);
349 } catch (std::exception&) { 348 } catch (std::exception&) {
350 - qpdf.warn(qpdf.damagedPDF( 349 + warn(damagedPDF(
351 "", -1, "error decoding candidate xref stream while recovering damaged file")); 350 "", -1, "error decoding candidate xref stream while recovering damaged file"));
352 } 351 }
353 QTC::TC("qpdf", "QPDF recover xref stream"); 352 QTC::TC("qpdf", "QPDF recover xref stream");
@@ -368,7 +367,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref) @@ -368,7 +367,7 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
368 } 367 }
369 if (root) { 368 if (root) {
370 if (!m->trailer) { 369 if (!m->trailer) {
371 - qpdf.warn(qpdf.damagedPDF( 370 + warn(damagedPDF(
372 "", -1, "unable to find trailer dictionary while recovering damaged file")); 371 "", -1, "unable to find trailer dictionary while recovering damaged file"));
373 m->trailer = QPDFObjectHandle::newDictionary(); 372 m->trailer = QPDFObjectHandle::newDictionary();
374 } 373 }
@@ -381,23 +380,20 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref) @@ -381,23 +380,20 @@ Objects::reconstruct_xref(QPDFExc&amp; e, bool found_startxref)
381 // could try to get the trailer from there. This may make it possible to recover files with 380 // could try to get the trailer from there. This may make it possible to recover files with
382 // bad startxref pointers even when they have object streams. 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 if (m->xref_table.empty()) { 385 if (m->xref_table.empty()) {
388 // We cannot check for an empty xref table in parse because empty tables are valid when 386 // We cannot check for an empty xref table in parse because empty tables are valid when
389 // creating QPDF objects from JSON. 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 check_warnings(); 390 check_warnings();
393 if (!m->parsed) { 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 // We could iterate through the objects looking for streams and try to find objects inside of 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,7 +439,7 @@ Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
443 // where it is terminated by arbitrary whitespace. 439 // where it is terminated by arbitrary whitespace.
444 if ((strncmp(buf, "xref", 4) == 0) && util::is_space(buf[4])) { 440 if ((strncmp(buf, "xref", 4) == 0) && util::is_space(buf[4])) {
445 if (skipped_space) { 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 QTC::TC( 444 QTC::TC(
449 "qpdf", 445 "qpdf",
@@ -462,12 +458,12 @@ Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery) @@ -462,12 +458,12 @@ Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
462 xref_offset = read_xrefStream(xref_offset, in_stream_recovery); 458 xref_offset = read_xrefStream(xref_offset, in_stream_recovery);
463 } 459 }
464 if (visited.contains(xref_offset)) { 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 if (!m->trailer) { 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 int size = m->trailer.getKey("/Size").getIntValueAsInt(); 468 int size = m->trailer.getKey("/Size").getIntValueAsInt();
473 int max_obj = 0; 469 int max_obj = 0;
@@ -478,7 +474,7 @@ Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery) @@ -478,7 +474,7 @@ Objects::read_xref(qpdf_offset_t xref_offset, bool in_stream_recovery)
478 max_obj = std::max(max_obj, *(m->deleted_objects.rbegin())); 474 max_obj = std::max(max_obj, *(m->deleted_objects.rbegin()));
479 } 475 }
480 if ((size < 1) || (size - 1 != max_obj)) { 476 if ((size < 1) || (size - 1 != max_obj)) {
481 - qpdf.warn(qpdf.damagedPDF( 477 + warn(damagedPDF(
482 "", 478 "",
483 -1, 479 -1,
484 ("reported number of objects (" + std::to_string(size) + 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,7 +611,7 @@ Objects::read_bad_xrefEntry(qpdf_offset_t&amp; f1, int&amp; f2, char&amp; type)
615 } 611 }
616 612
617 if (invalid) { 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 f1 = QUtil::string_to_ll(f1_str.c_str()); 617 f1 = QUtil::string_to_ll(f1_str.c_str());
@@ -692,7 +688,7 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset) @@ -692,7 +688,7 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset)
692 int num = 0; 688 int num = 0;
693 int bytes = 0; 689 int bytes = 0;
694 if (!parse_xrefFirst(line, obj, num, bytes)) { 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 m->file->seek(m->file->getLastOffset() + bytes, SEEK_SET); 693 m->file->seek(m->file->getLastOffset() + bytes, SEEK_SET);
698 for (qpdf_offset_t i = obj; i - num < obj; ++i) { 694 for (qpdf_offset_t i = obj; i - num < obj; ++i) {
@@ -705,7 +701,7 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset) @@ -705,7 +701,7 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset)
705 int f2 = 0; 701 int f2 = 0;
706 char type = '\0'; 702 char type = '\0';
707 if (!read_xrefEntry(f1, f2, type)) { 703 if (!read_xrefEntry(f1, f2, type)) {
708 - throw qpdf.damagedPDF( 704 + throw damagedPDF(
709 "xref table", "invalid xref entry (obj=" + std::to_string(i) + ")"); 705 "xref table", "invalid xref entry (obj=" + std::to_string(i) + ")");
710 } 706 }
711 if (type == 'f') { 707 if (type == 'f') {
@@ -725,17 +721,17 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset) @@ -725,17 +721,17 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset)
725 // Set offset to previous xref table if any 721 // Set offset to previous xref table if any
726 QPDFObjectHandle cur_trailer = m->objects.readTrailer(); 722 QPDFObjectHandle cur_trailer = m->objects.readTrailer();
727 if (!cur_trailer.isDictionary()) { 723 if (!cur_trailer.isDictionary()) {
728 - throw qpdf.damagedPDF("", "expected trailer dictionary"); 724 + throw damagedPDF("", "expected trailer dictionary");
729 } 725 }
730 726
731 if (!m->trailer) { 727 if (!m->trailer) {
732 setTrailer(cur_trailer); 728 setTrailer(cur_trailer);
733 729
734 if (!m->trailer.hasKey("/Size")) { 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 if (!m->trailer.getKey("/Size").isInteger()) { 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,14 +744,14 @@ Objects::read_xrefTable(qpdf_offset_t xref_offset)
748 // /Prev key instead of the xref stream's. 744 // /Prev key instead of the xref stream's.
749 (void)read_xrefStream(cur_trailer.getKey("/XRefStm").getIntValue()); 745 (void)read_xrefStream(cur_trailer.getKey("/XRefStm").getIntValue());
750 } else { 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 if (cur_trailer.hasKey("/Prev")) { 752 if (cur_trailer.hasKey("/Prev")) {
757 if (!cur_trailer.getKey("/Prev").isInteger()) { 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 return cur_trailer.getKey("/Prev").getIntValue(); 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,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 return 0; // unreachable 781 return 0; // unreachable
786 } 782 }
787 783
@@ -912,7 +908,7 @@ Objects::processXRefStream( @@ -912,7 +908,7 @@ Objects::processXRefStream(
912 qpdf_offset_t xref_offset, QPDFObjectHandle& xref_obj, bool in_stream_recovery) 908 qpdf_offset_t xref_offset, QPDFObjectHandle& xref_obj, bool in_stream_recovery)
913 { 909 {
914 auto damaged = [this, xref_offset](std::string_view msg) -> QPDFExc { 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 auto dict = xref_obj.getDict(); 914 auto dict = xref_obj.getDict();
@@ -932,7 +928,7 @@ Objects::processXRefStream( @@ -932,7 +928,7 @@ Objects::processXRefStream(
932 if (expected_size > actual_size) { 928 if (expected_size > actual_size) {
933 throw x; 929 throw x;
934 } else { 930 } else {
935 - qpdf.warn(x); 931 + warn(x);
936 } 932 }
937 } 933 }
938 934
@@ -992,7 +988,7 @@ Objects::processXRefStream( @@ -992,7 +988,7 @@ Objects::processXRefStream(
992 988
993 if (dict.hasKey("/Prev")) { 989 if (dict.hasKey("/Prev")) {
994 if (!dict.getKey("/Prev").isInteger()) { 990 if (!dict.getKey("/Prev").isInteger()) {
995 - throw qpdf.damagedPDF( 991 + throw damagedPDF(
996 "xref stream", "/Prev key in xref stream dictionary is not an integer"); 992 "xref stream", "/Prev key in xref stream dictionary is not an integer");
997 } 993 }
998 return dict.getKey("/Prev").getIntValue(); 994 return dict.getKey("/Prev").getIntValue();
@@ -1030,13 +1026,13 @@ Objects::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2) @@ -1030,13 +1026,13 @@ Objects::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1030 1026
1031 if (f0 == 2) { 1027 if (f0 == 2) {
1032 if (f1 == obj) { 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 return; 1031 return;
1036 } 1032 }
1037 if (f1 > m->xref_table_max_id) { 1033 if (f1 > m->xref_table_max_id) {
1038 // ignore impossibly large object stream ids 1034 // ignore impossibly large object stream ids
1039 - qpdf.warn(qpdf.damagedPDF( 1035 + warn(damagedPDF(
1040 "xref stream", 1036 "xref stream",
1041 "object stream id " + std::to_string(f1) + " for object " + std::to_string(obj) + 1037 "object stream id " + std::to_string(f1) + " for object " + std::to_string(obj) +
1042 " is impossibly large")); 1038 " is impossibly large"));
@@ -1061,8 +1057,7 @@ Objects::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2) @@ -1061,8 +1057,7 @@ Objects::insertXrefEntry(int obj, int f0, qpdf_offset_t f1, int f2)
1061 break; 1057 break;
1062 1058
1063 default: 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 break; 1061 break;
1067 } 1062 }
1068 } 1063 }
@@ -1182,9 +1177,9 @@ Objects::readTrailer() @@ -1182,9 +1177,9 @@ Objects::readTrailer()
1182 if (empty) { 1177 if (empty) {
1183 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in 1178 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1184 // actual PDF files and Adobe Reader appears to ignore them. 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 } else if (object.isDictionary() && m->objects.readToken(*m->file).isWord("stream")) { 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 // Override last_offset so that it points to the beginning of the object we just read 1184 // Override last_offset so that it points to the beginning of the object we just read
1190 m->file->setLastOffset(offset); 1185 m->file->setLastOffset(offset);
@@ -1210,8 +1205,7 @@ Objects::readObject(std::string const&amp; description, QPDFObjGen og) @@ -1210,8 +1205,7 @@ Objects::readObject(std::string const&amp; description, QPDFObjGen og)
1210 if (empty) { 1205 if (empty) {
1211 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in 1206 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1212 // actual PDF files and Adobe Reader appears to ignore them. 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 return object; 1209 return object;
1216 } 1210 }
1217 auto token = readToken(*m->file); 1211 auto token = readToken(*m->file);
@@ -1220,7 +1214,7 @@ Objects::readObject(std::string const&amp; description, QPDFObjGen og) @@ -1220,7 +1214,7 @@ Objects::readObject(std::string const&amp; description, QPDFObjGen og)
1220 token = readToken(*m->file); 1214 token = readToken(*m->file);
1221 } 1215 }
1222 if (!token.isWord("endobj")) { 1216 if (!token.isWord("endobj")) {
1223 - qpdf.warn(qpdf.damagedPDF("expected endobj")); 1217 + warn(damagedPDF("expected endobj"));
1224 } 1218 }
1225 return object; 1219 return object;
1226 } 1220 }
@@ -1241,9 +1235,9 @@ Objects::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offse @@ -1241,9 +1235,9 @@ Objects::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offse
1241 1235
1242 if (!length_obj.isInteger()) { 1236 if (!length_obj.isInteger()) {
1243 if (length_obj.null()) { 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 length = toS(length_obj.getUIntValue()); 1243 length = toS(length_obj.getUIntValue());
@@ -1251,11 +1245,11 @@ Objects::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offse @@ -1251,11 +1245,11 @@ Objects::readStream(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_offset_t offse
1251 m->file->seek(stream_offset, SEEK_SET); 1245 m->file->seek(stream_offset, SEEK_SET);
1252 m->file->seek(toO(length), SEEK_CUR); 1246 m->file->seek(toO(length), SEEK_CUR);
1253 if (!readToken(*m->file).isWord("endstream")) { 1247 if (!readToken(*m->file).isWord("endstream")) {
1254 - throw qpdf.damagedPDF("expected endstream"); 1248 + throw damagedPDF("expected endstream");
1255 } 1249 }
1256 } catch (QPDFExc& e) { 1250 } catch (QPDFExc& e) {
1257 if (m->attempt_recovery) { 1251 if (m->attempt_recovery) {
1258 - qpdf.warn(e); 1252 + warn(e);
1259 length = recoverStreamLength(m->file, og, stream_offset); 1253 length = recoverStreamLength(m->file, og, stream_offset);
1260 } else { 1254 } else {
1261 throw; 1255 throw;
@@ -1295,7 +1289,7 @@ Objects::validateStreamLineEnd(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_off @@ -1295,7 +1289,7 @@ Objects::validateStreamLineEnd(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_off
1295 // Treat the \r by itself as the whitespace after endstream and start reading 1289 // Treat the \r by itself as the whitespace after endstream and start reading
1296 // stream data in spite of not having seen a newline. 1290 // stream data in spite of not having seen a newline.
1297 m->file->unreadCh(ch); 1291 m->file->unreadCh(ch);
1298 - qpdf.warn(qpdf.damagedPDF( 1292 + warn(damagedPDF(
1299 m->file->tell(), "stream keyword followed by carriage return only")); 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,12 +1297,11 @@ Objects::validateStreamLineEnd(QPDFObjectHandle&amp; object, QPDFObjGen og, qpdf_off
1303 } 1297 }
1304 if (!util::is_space(ch)) { 1298 if (!util::is_space(ch)) {
1305 m->file->unreadCh(ch); 1299 m->file->unreadCh(ch);
1306 - qpdf.warn(qpdf.damagedPDF( 1300 + warn(damagedPDF(
1307 m->file->tell(), "stream keyword not followed by proper line terminator")); 1301 m->file->tell(), "stream keyword not followed by proper line terminator"));
1308 return; 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,7 +1312,7 @@ Objects::readObjectInStream(is::OffsetBuffer&amp; input, int stream_id, int obj_id)
1319 if (empty) { 1312 if (empty) {
1320 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in 1313 // Nothing in the PDF spec appears to allow empty objects, but they have been encountered in
1321 // actual PDF files and Adobe Reader appears to ignore them. 1314 // actual PDF files and Adobe Reader appears to ignore them.
1322 - qpdf.warn(QPDFExc( 1315 + warn(QPDFExc(
1323 qpdf_e_damaged_pdf, 1316 qpdf_e_damaged_pdf,
1324 m->file->getName() + " object stream " + std::to_string(stream_id), 1317 m->file->getName() + " object stream " + std::to_string(stream_id),
1325 +"object " + std::to_string(obj_id) + " 0, offset " + 1318 +"object " + std::to_string(obj_id) + " 0, offset " +
@@ -1347,7 +1340,7 @@ Objects::recoverStreamLength( @@ -1347,7 +1340,7 @@ Objects::recoverStreamLength(
1347 std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset) 1340 std::shared_ptr<InputSource> input, QPDFObjGen og, qpdf_offset_t stream_offset)
1348 { 1341 {
1349 // Try to reconstruct stream length by looking for endstream or endobj 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 PatternFinder ef(qpdf, &QPDF::findEndstream); 1345 PatternFinder ef(qpdf, &QPDF::findEndstream);
1353 size_t length = 0; 1346 size_t length = 0;
@@ -1386,10 +1379,10 @@ Objects::recoverStreamLength( @@ -1386,10 +1379,10 @@ Objects::recoverStreamLength(
1386 } 1379 }
1387 1380
1388 if (length == 0) { 1381 if (length == 0) {
1389 - qpdf.warn(qpdf.damagedPDF( 1382 + warn(damagedPDF(
1390 *input, stream_offset, "unable to recover stream data; treating stream as empty")); 1383 *input, stream_offset, "unable to recover stream data; treating stream as empty"));
1391 } else { 1384 } else {
1392 - qpdf.warn(qpdf.damagedPDF( 1385 + warn(damagedPDF(
1393 *input, stream_offset, "recovered stream length: " + std::to_string(length))); 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,24 +1402,24 @@ Objects::read_object_start(qpdf_offset_t offset)
1409 QPDFTokenizer::Token tobjid = readToken(*m->file); 1402 QPDFTokenizer::Token tobjid = readToken(*m->file);
1410 bool objidok = tobjid.isInteger(); 1403 bool objidok = tobjid.isInteger();
1411 if (!objidok) { 1404 if (!objidok) {
1412 - throw qpdf.damagedPDF(offset, "expected n n obj"); 1405 + throw damagedPDF(offset, "expected n n obj");
1413 } 1406 }
1414 QPDFTokenizer::Token tgen = readToken(*m->file); 1407 QPDFTokenizer::Token tgen = readToken(*m->file);
1415 bool genok = tgen.isInteger(); 1408 bool genok = tgen.isInteger();
1416 if (!genok) { 1409 if (!genok) {
1417 - throw qpdf.damagedPDF(offset, "expected n n obj"); 1410 + throw damagedPDF(offset, "expected n n obj");
1418 } 1411 }
1419 QPDFTokenizer::Token tobj = readToken(*m->file); 1412 QPDFTokenizer::Token tobj = readToken(*m->file);
1420 1413
1421 bool objok = tobj.isWord("obj"); 1414 bool objok = tobj.isWord("obj");
1422 1415
1423 if (!objok) { 1416 if (!objok) {
1424 - throw qpdf.damagedPDF(offset, "expected n n obj"); 1417 + throw damagedPDF(offset, "expected n n obj");
1425 } 1418 }
1426 int objid = QUtil::string_to_int(tobjid.getValue().c_str()); 1419 int objid = QUtil::string_to_int(tobjid.getValue().c_str());
1427 int generation = QUtil::string_to_int(tgen.getValue().c_str()); 1420 int generation = QUtil::string_to_int(tgen.getValue().c_str());
1428 if (objid == 0) { 1421 if (objid == 0) {
1429 - throw qpdf.damagedPDF(offset, "object with ID 0"); 1422 + throw damagedPDF(offset, "object with ID 0");
1430 } 1423 }
1431 return {objid, generation}; 1424 return {objid, generation};
1432 } 1425 }
@@ -1447,20 +1440,20 @@ Objects::readObjectAtOffset( @@ -1447,20 +1440,20 @@ Objects::readObjectAtOffset(
1447 // "0000000000 00000 n", which is not correct, but it won't hurt anything for us to ignore 1440 // "0000000000 00000 n", which is not correct, but it won't hurt anything for us to ignore
1448 // these. 1441 // these.
1449 if (offset == 0) { 1442 if (offset == 0) {
1450 - qpdf.warn(qpdf.damagedPDF(-1, "object has offset 0")); 1443 + warn(damagedPDF(-1, "object has offset 0"));
1451 return; 1444 return;
1452 } 1445 }
1453 1446
1454 try { 1447 try {
1455 og = read_object_start(offset); 1448 og = read_object_start(offset);
1456 if (exp_og != og) { 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 if (try_recovery) { 1451 if (try_recovery) {
1459 // Will be retried below 1452 // Will be retried below
1460 throw e; 1453 throw e;
1461 } else { 1454 } else {
1462 // We can try reading the object anyway even if the ID doesn't match. 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 } catch (QPDFExc& e) { 1459 } catch (QPDFExc& e) {
@@ -1474,7 +1467,7 @@ Objects::readObjectAtOffset( @@ -1474,7 +1467,7 @@ Objects::readObjectAtOffset(
1474 readObjectAtOffset(false, new_offset, description, exp_og); 1467 readObjectAtOffset(false, new_offset, description, exp_og);
1475 return; 1468 return;
1476 } 1469 }
1477 - qpdf.warn(qpdf.damagedPDF( 1470 + warn(damagedPDF(
1478 "", 1471 "",
1479 -1, 1472 -1,
1480 ("object " + exp_og.unparse(' ') + 1473 ("object " + exp_og.unparse(' ') +
@@ -1493,7 +1486,7 @@ Objects::readObjectAtOffset( @@ -1493,7 +1486,7 @@ Objects::readObjectAtOffset(
1493 while (true) { 1486 while (true) {
1494 char ch; 1487 char ch;
1495 if (!m->file->read(&ch, 1)) { 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 if (!isspace(static_cast<unsigned char>(ch))) { 1491 if (!isspace(static_cast<unsigned char>(ch))) {
1499 m->file->seek(-1, SEEK_CUR); 1492 m->file->seek(-1, SEEK_CUR);
@@ -1552,7 +1545,7 @@ Objects::readObjectAtOffset( @@ -1552,7 +1545,7 @@ Objects::readObjectAtOffset(
1552 while (true) { 1545 while (true) {
1553 char ch; 1546 char ch;
1554 if (!m->file->read(&ch, 1)) { 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 if (!isspace(static_cast<unsigned char>(ch))) { 1550 if (!isspace(static_cast<unsigned char>(ch))) {
1558 m->file->seek(-1, SEEK_CUR); 1551 m->file->seek(-1, SEEK_CUR);
@@ -1574,7 +1567,7 @@ Objects::resolve(QPDFObjGen og) @@ -1574,7 +1567,7 @@ Objects::resolve(QPDFObjGen og)
1574 if (m->resolving.contains(og)) { 1567 if (m->resolving.contains(og)) {
1575 // This can happen if an object references itself directly or indirectly in some key that 1568 // This can happen if an object references itself directly or indirectly in some key that
1576 // has to be resolved during object parsing, such as stream length. 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 updateCache(og, QPDFObject::create<QPDF_Null>(), -1, -1); 1571 updateCache(og, QPDFObject::create<QPDF_Null>(), -1, -1);
1579 return m->obj_cache[og].object; 1572 return m->obj_cache[og].object;
1580 } 1573 }
@@ -1594,13 +1587,13 @@ Objects::resolve(QPDFObjGen og) @@ -1594,13 +1587,13 @@ Objects::resolve(QPDFObjGen og)
1594 break; 1587 break;
1595 1588
1596 default: 1589 default:
1597 - throw qpdf.damagedPDF( 1590 + throw damagedPDF(
1598 "", -1, ("object " + og.unparse('/') + " has unexpected xref entry type")); 1591 "", -1, ("object " + og.unparse('/') + " has unexpected xref entry type"));
1599 } 1592 }
1600 } catch (QPDFExc& e) { 1593 } catch (QPDFExc& e) {
1601 - qpdf.warn(e); 1594 + warn(e);
1602 } catch (std::exception& e) { 1595 } catch (std::exception& e) {
1603 - qpdf.warn(qpdf.damagedPDF( 1596 + warn(damagedPDF(
1604 "", -1, ("object " + og.unparse('/') + ": error reading object: " + e.what()))); 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,7 +1629,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1636 // Force resolution of object stream 1629 // Force resolution of object stream
1637 Stream obj_stream = qpdf.getObject(obj_stream_number, 0); 1630 Stream obj_stream = qpdf.getObject(obj_stream_number, 0);
1638 if (!obj_stream) { 1631 if (!obj_stream) {
1639 - throw qpdf.damagedPDF( 1632 + throw damagedPDF(
1640 "object " + std::to_string(obj_stream_number) + " 0", 1633 "object " + std::to_string(obj_stream_number) + " 0",
1641 "supposed object stream " + std::to_string(obj_stream_number) + " is not a stream"); 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,7 +1642,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1649 1642
1650 QPDFObjectHandle dict = obj_stream.getDict(); 1643 QPDFObjectHandle dict = obj_stream.getDict();
1651 if (!dict.isDictionaryOfType("/ObjStm")) { 1644 if (!dict.isDictionaryOfType("/ObjStm")) {
1652 - qpdf.warn(qpdf.damagedPDF( 1645 + warn(damagedPDF(
1653 "object " + std::to_string(obj_stream_number) + " 0", 1646 "object " + std::to_string(obj_stream_number) + " 0",
1654 "supposed object stream " + std::to_string(obj_stream_number) + " has wrong type")); 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,7 +1650,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1657 unsigned int n{0}; 1650 unsigned int n{0};
1658 int first{0}; 1651 int first{0};
1659 if (!(dict.getKey("/N").getValueAsUInt(n) && dict.getKey("/First").getValueAsInt(first))) { 1652 if (!(dict.getKey("/N").getValueAsUInt(n) && dict.getKey("/First").getValueAsInt(first))) {
1660 - throw qpdf.damagedPDF( 1653 + throw damagedPDF(
1661 "object " + std::to_string(obj_stream_number) + " 0", 1654 "object " + std::to_string(obj_stream_number) + " 0",
1662 "object stream " + std::to_string(obj_stream_number) + " has incorrect keys"); 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,7 +1667,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1674 auto b_start = stream_data.data(); 1667 auto b_start = stream_data.data();
1675 1668
1676 if (first >= end_offset) { 1669 if (first >= end_offset) {
1677 - throw qpdf.damagedPDF( 1670 + throw damagedPDF(
1678 "object " + std::to_string(obj_stream_number) + " 0", 1671 "object " + std::to_string(obj_stream_number) + " 0",
1679 "object stream " + std::to_string(obj_stream_number) + " has invalid /First entry"); 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,17 +1687,17 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1694 long long offset = QUtil::string_to_int(toffset.getValue().c_str()); 1687 long long offset = QUtil::string_to_int(toffset.getValue().c_str());
1695 1688
1696 if (num == obj_stream_number) { 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 continue; 1691 continue;
1699 } 1692 }
1700 1693
1701 if (num < 1) { 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 continue; 1696 continue;
1704 } 1697 }
1705 1698
1706 if (offset <= last_offset) { 1699 if (offset <= last_offset) {
1707 - qpdf.warn(damaged( 1700 + warn(damaged(
1708 num, 1701 num,
1709 input.getLastOffset(), 1702 input.getLastOffset(),
1710 "offset " + std::to_string(offset) + 1703 "offset " + std::to_string(offset) +
@@ -1718,7 +1711,7 @@ Objects::resolveObjectsInStream(int obj_stream_number) @@ -1718,7 +1711,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1718 } 1711 }
1719 1712
1720 if (first + offset >= end_offset) { 1713 if (first + offset >= end_offset) {
1721 - qpdf.warn(damaged( 1714 + warn(damaged(
1722 num, input.getLastOffset(), "offset " + std::to_string(offset) + " is too large")); 1715 num, input.getLastOffset(), "offset " + std::to_string(offset) + " is too large"));
1723 continue; 1716 continue;
1724 } 1717 }
@@ -1934,7 +1927,7 @@ Objects::tableSize() @@ -1934,7 +1927,7 @@ Objects::tableSize()
1934 // Temporary fix. Long-term solution is 1927 // Temporary fix. Long-term solution is
1935 // - QPDFObjGen to enforce objgens are valid and sensible 1928 // - QPDFObjGen to enforce objgens are valid and sensible
1936 // - xref table and obj cache to protect against insertion of impossibly large obj ids 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 if (max_obj < 1.1 * std::max(toI(m->obj_cache.size()), max_xref)) { 1932 if (max_obj < 1.1 * std::max(toI(m->obj_cache.size()), max_xref)) {
1940 return toS(++max_obj); 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 #include <qpdf/QPDF_private.hh> 2 #include <qpdf/QPDF_private.hh>
2 3
  4 +#include <qpdf/QPDFAcroFormDocumentHelper.hh>
3 #include <qpdf/QPDFExc.hh> 5 #include <qpdf/QPDFExc.hh>
4 #include <qpdf/QPDFObjectHandle_private.hh> 6 #include <qpdf/QPDFObjectHandle_private.hh>
5 #include <qpdf/QTC.hh> 7 #include <qpdf/QTC.hh>
6 #include <qpdf/QUtil.hh> 8 #include <qpdf/QUtil.hh>
  9 +#include <qpdf/Util.hh>
7 10
8 // In support of page manipulation APIs, these methods internally maintain state about pages in a 11 // In support of page manipulation APIs, these methods internally maintain state about pages in a
9 // pair of data structures: all_pages, which is a vector of page objects, and pageobj_to_pages_pos, 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,10 +45,16 @@ using Pages = QPDF::Doc::Pages;
42 std::vector<QPDFObjectHandle> const& 45 std::vector<QPDFObjectHandle> const&
43 QPDF::getAllPages() 46 QPDF::getAllPages()
44 { 47 {
  48 + return m->pages.all();
  49 +}
  50 +
  51 +std::vector<QPDFObjectHandle> const&
  52 +Pages::cache()
  53 +{
45 // Note that pushInheritedAttributesToPage may also be used to initialize m->all_pages. 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 QPDFObjGen::set visited; 58 QPDFObjGen::set visited;
50 QPDFObjGen::set seen; 59 QPDFObjGen::set seen;
51 QPDFObjectHandle pages = root.getKey("/Pages"); 60 QPDFObjectHandle pages = root.getKey("/Pages");
@@ -77,18 +86,18 @@ QPDF::getAllPages() @@ -77,18 +86,18 @@ QPDF::getAllPages()
77 qpdf_e_pages, m->file->getName(), "", 0, "root of pages tree has no /Kids array"); 86 qpdf_e_pages, m->file->getName(), "", 0, "root of pages tree has no /Kids array");
78 } 87 }
79 try { 88 try {
80 - m->pages.getAllPagesInternal(pages, visited, seen, false, false); 89 + getAllPagesInternal(pages, visited, seen, false, false);
81 } catch (...) { 90 } catch (...) {
82 - m->all_pages.clear();  
83 - m->invalid_page_found = false; 91 + all_pages.clear();
  92 + invalid_page_found = false;
84 throw; 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 void 103 void
@@ -137,7 +146,7 @@ Pages::getAllPagesInternal( @@ -137,7 +146,7 @@ Pages::getAllPagesInternal(
137 146
138 if (!kid.isDictionary()) { 147 if (!kid.isDictionary()) {
139 kid.warn("Pages tree includes non-dictionary object; ignoring"); 148 kid.warn("Pages tree includes non-dictionary object; ignoring");
140 - m->invalid_page_found = true; 149 + invalid_page_found = true;
141 continue; 150 continue;
142 } 151 }
143 if (!kid.isIndirect()) { 152 if (!kid.isIndirect()) {
@@ -206,7 +215,7 @@ Pages::getAllPagesInternal( @@ -206,7 +215,7 @@ Pages::getAllPagesInternal(
206 cur_node.warn( 215 cur_node.warn(
207 "kid " + std::to_string(i) + 216 "kid " + std::to_string(i) +
208 " (from 0) appears more than once in the pages tree; ignoring duplicate"); 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 kid = QPDFObjectHandle::newNull(); 219 kid = QPDFObjectHandle::newNull();
211 continue; 220 continue;
212 } 221 }
@@ -223,11 +232,11 @@ Pages::getAllPagesInternal( @@ -223,11 +232,11 @@ Pages::getAllPagesInternal(
223 if (m->reconstructed_xref && errors > 2) { 232 if (m->reconstructed_xref && errors > 2) {
224 cur_node.warn( 233 cur_node.warn(
225 "kid " + std::to_string(i) + " (from 0) has too many errors; ignoring page"); 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 kid = QPDFObjectHandle::newNull(); 236 kid = QPDFObjectHandle::newNull();
228 continue; 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,13 +244,19 @@ Pages::getAllPagesInternal(
235 void 244 void
236 QPDF::updateAllPagesCache() 245 QPDF::updateAllPagesCache()
237 { 246 {
  247 + m->pages.update_cache();
  248 +}
  249 +
  250 +void
  251 +Pages::update_cache()
  252 +{
238 // Force regeneration of the pages cache. We force immediate recalculation of all_pages since 253 // Force regeneration of the pages cache. We force immediate recalculation of all_pages since
239 // users may have references to it that they got from calls to getAllPages(). We can defer 254 // users may have references to it that they got from calls to getAllPages(). We can defer
240 // recalculation of pageobj_to_pages_pos until needed. 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 void 262 void
@@ -249,30 +264,30 @@ Pages::flattenPagesTree() @@ -249,30 +264,30 @@ Pages::flattenPagesTree()
249 { 264 {
250 // If not already done, flatten the /Pages structure and initialize pageobj_to_pages_pos. 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 return; 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 // generated. 272 // generated.
258 pushInheritedAttributesToPage(true, true); 273 pushInheritedAttributesToPage(true, true);
259 274
260 QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages"); 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 for (size_t pos = 0; pos < len; ++pos) { 278 for (size_t pos = 0; pos < len; ++pos) {
264 // Populate pageobj_to_pages_pos and fix parent pointer. There should be no duplicates at 279 // Populate pageobj_to_pages_pos and fix parent pointer. There should be no duplicates at
265 // this point because pushInheritedAttributesToPage calls getAllPages which resolves 280 // this point because pushInheritedAttributesToPage calls getAllPages which resolves
266 // duplicates. 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 // /Count has not changed 287 // /Count has not changed
273 if (pages.getKey("/Count").getUIntValue() != len) { 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 } else { 291 } else {
277 throw std::runtime_error("/Count is wrong after flattening pages tree"); 292 throw std::runtime_error("/Count is wrong after flattening pages tree");
278 } 293 }
@@ -280,11 +295,141 @@ Pages::flattenPagesTree() @@ -280,11 +295,141 @@ Pages::flattenPagesTree()
280 } 295 }
281 296
282 void 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 Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate) 428 Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate)
284 { 429 {
285 QPDFObjGen og(obj.getObjGen()); 430 QPDFObjGen og(obj.getObjGen());
286 if (check_duplicate) { 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 // The library never calls insertPageobjToPage in a way that causes this to happen. 433 // The library never calls insertPageobjToPage in a way that causes this to happen.
289 throw QPDFExc( 434 throw QPDFExc(
290 qpdf_e_pages, 435 qpdf_e_pages,
@@ -294,52 +439,51 @@ Pages::insertPageobjToPage(QPDFObjectHandle const&amp; obj, int pos, bool check_dupl @@ -294,52 +439,51 @@ Pages::insertPageobjToPage(QPDFObjectHandle const&amp; obj, int pos, bool check_dupl
294 "duplicate page reference found; this would cause loss of data"); 439 "duplicate page reference found; this would cause loss of data");
295 } 440 }
296 } else { 441 } else {
297 - m->pageobj_to_pages_pos[og] = pos; 442 + pageobj_to_pages_pos[og] = pos;
298 } 443 }
299 } 444 }
300 445
301 void 446 void
302 -Pages::insertPage(QPDFObjectHandle newpage, int pos) 447 +Pages::insert(QPDFObjectHandle newpage, int pos)
303 { 448 {
304 // pos is numbered from 0, so pos = 0 inserts at the beginning and pos = npages adds to the end. 449 // pos is numbered from 0, so pos = 0 inserts at the beginning and pos = npages adds to the end.
305 450
306 flattenPagesTree(); 451 flattenPagesTree();
307 452
308 - if (!newpage.isIndirect()) { 453 + if (!newpage.indirect()) {
309 newpage = qpdf.makeIndirectObject(newpage); 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 newpage = qpdf.copyForeignObject(newpage); 457 newpage = qpdf.copyForeignObject(newpage);
313 } else { 458 } else {
314 QTC::TC("qpdf", "QPDF insert indirect page"); 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 throw std::runtime_error("QPDF::insertPage called with pos out of range"); 463 throw std::runtime_error("QPDF::insertPage called with pos out of range");
319 } 464 }
320 465
321 QTC::TC( 466 QTC::TC(
322 "qpdf", 467 "qpdf",
323 "QPDF insert page", 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 newpage.replaceKey("/Parent", pages); 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 insertPageobjToPage(newpage, pos, true); 488 insertPageobjToPage(newpage, pos, true);
345 } 489 }
@@ -347,24 +491,30 @@ Pages::insertPage(QPDFObjectHandle newpage, int pos) @@ -347,24 +491,30 @@ Pages::insertPage(QPDFObjectHandle newpage, int pos)
347 void 491 void
348 QPDF::removePage(QPDFObjectHandle page) 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 QTC::TC( 501 QTC::TC(
352 "qpdf", 502 "qpdf",
353 "QPDF remove page", 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 QPDFObjectHandle kids = pages.getKey("/Kids"); 509 QPDFObjectHandle kids = pages.getKey("/Kids");
360 510
361 kids.eraseItem(pos); 511 kids.eraseItem(pos);
362 int npages = static_cast<int>(kids.size()); 512 int npages = static_cast<int>(kids.size());
363 pages.replaceKey("/Count", QPDFObjectHandle::newInteger(npages)); 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 for (int i = pos; i < npages; ++i) { 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,17 +525,16 @@ QPDF::addPageAt(QPDFObjectHandle newpage, bool before, QPDFObjectHandle refpage)
375 if (!before) { 525 if (!before) {
376 ++refpos; 526 ++refpos;
377 } 527 }
378 - m->pages.insertPage(newpage, refpos); 528 + m->pages.insert(newpage, refpos);
379 } 529 }
380 530
381 void 531 void
382 QPDF::addPage(QPDFObjectHandle newpage, bool first) 532 QPDF::addPage(QPDFObjectHandle newpage, bool first)
383 { 533 {
384 if (first) { 534 if (first) {
385 - m->pages.insertPage(newpage, 0); 535 + m->pages.insert(newpage, 0);
386 } else { 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,9 +547,15 @@ QPDF::findPage(QPDFObjectHandle&amp; page)
398 int 547 int
399 QPDF::findPage(QPDFObjGen og) 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 throw QPDFExc( 559 throw QPDFExc(
405 qpdf_e_pages, 560 qpdf_e_pages,
406 m->file->getName(), 561 m->file->getName(),
@@ -410,3 +565,168 @@ QPDF::findPage(QPDFObjGen og) @@ -410,3 +565,168 @@ QPDF::findPage(QPDFObjGen og)
410 } 565 }
411 return (*it).second; 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 #include <qpdf/qpdf-c.h> 1 #include <qpdf/qpdf-c.h>
2 2
3 -#include <qpdf/QPDF.hh> 3 +#include <qpdf/QPDF_private.hh>
4 4
5 #include <qpdf/BufferInputSource.hh> 5 #include <qpdf/BufferInputSource.hh>
6 #include <qpdf/Pl_Buffer.hh> 6 #include <qpdf/Pl_Buffer.hh>
@@ -1804,10 +1804,9 @@ qpdf_oh_replace_stream_data( @@ -1804,10 +1804,9 @@ qpdf_oh_replace_stream_data(
1804 int 1804 int
1805 qpdf_get_num_pages(qpdf_data qpdf) 1805 qpdf_get_num_pages(qpdf_data qpdf)
1806 { 1806 {
1807 - QTC::TC("qpdf", "qpdf-c called qpdf_num_pages");  
1808 int n = -1; 1807 int n = -1;
1809 QPDF_ERROR_CODE code = 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 if (code & QPDF_ERRORS) { 1810 if (code & QPDF_ERRORS) {
1812 return -1; 1811 return -1;
1813 } 1812 }
@@ -1817,10 +1816,10 @@ qpdf_get_num_pages(qpdf_data qpdf) @@ -1817,10 +1816,10 @@ qpdf_get_num_pages(qpdf_data qpdf)
1817 qpdf_oh 1816 qpdf_oh
1818 qpdf_get_page_n(qpdf_data qpdf, size_t i) 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 qpdf_oh result = 0; 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 if ((code & QPDF_ERRORS) || (result == 0)) { 1823 if ((code & QPDF_ERRORS) || (result == 0)) {
1825 return qpdf_oh_new_uninitialized(qpdf); 1824 return qpdf_oh_new_uninitialized(qpdf);
1826 } 1825 }
libqpdf/qpdf/QPDFObjectHandle_private.hh
@@ -305,10 +305,10 @@ namespace qpdf @@ -305,10 +305,10 @@ namespace qpdf
305 explicit Integer(std::integral auto value) : 305 explicit Integer(std::integral auto value) :
306 Integer(static_cast<long long>(value)) 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 throw std::overflow_error("overflow constructing Integer"); 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,491 +295,65 @@ class QPDF::PatternFinder final: public InputSource::Finder
295 class QPDF::Doc 295 class QPDF::Doc
296 { 296 {
297 public: 297 public:
  298 + class Encryption;
298 class JobSetter; 299 class JobSetter;
  300 + class Linearization;
  301 + class Objects;
  302 + class Pages;
299 class ParseGuard; 303 class ParseGuard;
300 class Resolver; 304 class Resolver;
301 class Writer; 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 public: 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 void 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 QPDF& qpdf; 352 QPDF& qpdf;
740 QPDF::Members* m; 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 Doc() = delete; 358 Doc() = delete;
785 Doc(Doc const&) = delete; 359 Doc(Doc const&) = delete;
@@ -788,32 +362,17 @@ class QPDF::Doc @@ -788,32 +362,17 @@ class QPDF::Doc
788 Doc& operator=(Doc&&) = delete; 362 Doc& operator=(Doc&&) = delete;
789 ~Doc() = default; 363 ~Doc() = default;
790 364
791 - Doc(QPDF& qpdf, QPDF::Members& m) : 365 + Doc(QPDF& qpdf, QPDF::Members* m) :
792 qpdf(qpdf), 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 bool reconstructed_xref() const; 377 bool reconstructed_xref() const;
819 378
@@ -864,11 +423,7 @@ class QPDF::Doc @@ -864,11 +423,7 @@ class QPDF::Doc
864 423
865 private: 424 private:
866 QPDF& qpdf; 425 QPDF& qpdf;
867 - QPDF::Members& m;  
868 -  
869 - Linearization lin_;  
870 - Objects objects_;  
871 - Pages pages_; 426 + QPDF::Members* m;
872 427
873 // Document Helpers; 428 // Document Helpers;
874 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_; 429 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_;
@@ -878,7 +433,528 @@ class QPDF::Doc @@ -878,7 +433,528 @@ class QPDF::Doc
878 std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels_; 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 friend class QPDF; 959 friend class QPDF;
884 friend class ResolveRecorder; 960 friend class ResolveRecorder;
@@ -889,10 +965,10 @@ class QPDF::Members @@ -889,10 +965,10 @@ class QPDF::Members
889 ~Members() = default; 965 ~Members() = default;
890 966
891 private: 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 std::shared_ptr<QPDFLogger> log; 972 std::shared_ptr<QPDFLogger> log;
897 unsigned long long unique_id{0}; 973 unsigned long long unique_id{0};
898 qpdf::Tokenizer tokenizer; 974 qpdf::Tokenizer tokenizer;
@@ -915,12 +991,6 @@ class QPDF::Members @@ -915,12 +991,6 @@ class QPDF::Members
915 std::map<QPDFObjGen, ObjCache> obj_cache; 991 std::map<QPDFObjGen, ObjCache> obj_cache;
916 std::set<QPDFObjGen> resolving; 992 std::set<QPDFObjGen> resolving;
917 QPDFObjectHandle trailer; 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 std::vector<QPDFExc> warnings; 994 std::vector<QPDFExc> warnings;
925 bool reconstructed_xref{false}; 995 bool reconstructed_xref{false};
926 bool in_read_xref_stream{false}; 996 bool in_read_xref_stream{false};
@@ -978,25 +1048,46 @@ class QPDF::Doc::Resolver @@ -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 inline bool 1076 inline bool
982 QPDF::Doc::reconstructed_xref() const 1077 QPDF::Doc::reconstructed_xref() const
983 { 1078 {
984 - return m.reconstructed_xref; 1079 + return m->reconstructed_xref;
985 } 1080 }
986 1081
987 inline QPDF::Doc& 1082 inline QPDF::Doc&
988 QPDF::doc() 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 #endif // QPDF_PRIVATE_HH 1093 #endif // QPDF_PRIVATE_HH
qpdf/qpdf.testcov
@@ -224,14 +224,8 @@ QPDFAnnotationObjectHelper default matrix 0 @@ -224,14 +224,8 @@ QPDFAnnotationObjectHelper default matrix 0
224 QPDFAnnotationObjectHelper rotate 90 0 224 QPDFAnnotationObjectHelper rotate 90 0
225 QPDFAnnotationObjectHelper rotate 180 0 225 QPDFAnnotationObjectHelper rotate 180 0
226 QPDFAnnotationObjectHelper rotate 270 0 226 QPDFAnnotationObjectHelper rotate 270 0
227 -QPDFPageDocumentHelper skip widget need appearances 0  
228 -QPDFPageDocumentHelper merge DR 0  
229 QPDFPageDocumentHelper non-widget annotation 0 227 QPDFPageDocumentHelper non-widget annotation 0
230 -QPDFPageDocumentHelper remove annots 0  
231 -QPDFPageDocumentHelper replace indirect annots 0  
232 -QPDFPageDocumentHelper replace direct annots 0  
233 QPDFObjectHandle replace with copy 0 228 QPDFObjectHandle replace with copy 0
234 -QPDFPageDocumentHelper indirect as resources 0  
235 QPDFAnnotationObjectHelper forbidden flags 0 229 QPDFAnnotationObjectHelper forbidden flags 0
236 QPDFAnnotationObjectHelper missing required flags 0 230 QPDFAnnotationObjectHelper missing required flags 0
237 QPDFFormFieldObjectHelper checkbox kid widget 0 231 QPDFFormFieldObjectHelper checkbox kid widget 0
@@ -255,7 +249,6 @@ QPDFJob auto-encode password 0 @@ -255,7 +249,6 @@ QPDFJob auto-encode password 0
255 QPDFJob bytes fallback warning 0 249 QPDFJob bytes fallback warning 0
256 QPDFJob invalid utf-8 in auto 0 250 QPDFJob invalid utf-8 in auto 0
257 QPDFJob input password hex-bytes 0 251 QPDFJob input password hex-bytes 0
258 -QPDFPageDocumentHelper ignore annotation with no appearance 0  
259 QPDFFormFieldObjectHelper replaced BMC at EOF 0 252 QPDFFormFieldObjectHelper replaced BMC at EOF 0
260 QPDFFormFieldObjectHelper fallback Tf 0 253 QPDFFormFieldObjectHelper fallback Tf 0
261 QPDFPageObjectHelper copy shared attribute 1 254 QPDFPageObjectHelper copy shared attribute 1
@@ -383,8 +376,6 @@ qpdf-c warn about oh error 1 @@ -383,8 +376,6 @@ qpdf-c warn about oh error 1
383 qpdf-c cleanup warned about unhandled error 0 376 qpdf-c cleanup warned about unhandled error 0
384 qpdf-c called qpdf_get_object_by_id 0 377 qpdf-c called qpdf_get_object_by_id 0
385 qpdf-c called qpdf_replace_object 0 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 qpdf-c called qpdf_update_all_pages_cache 0 379 qpdf-c called qpdf_update_all_pages_cache 0
389 qpdf-c called qpdf_find_page_by_id 0 380 qpdf-c called qpdf_find_page_by_id 0
390 qpdf-c called qpdf_find_page_by_oh 0 381 qpdf-c called qpdf_find_page_by_oh 0
@@ -422,14 +413,9 @@ qpdf-c called qpdf_empty_pdf 0 @@ -422,14 +413,9 @@ qpdf-c called qpdf_empty_pdf 0
422 QPDF_json missing qpdf 0 413 QPDF_json missing qpdf 0
423 QPDF_json missing pdf version 0 414 QPDF_json missing pdf version 0
424 QPDF_json top-level scalar 0 415 QPDF_json top-level scalar 0
425 -QPDF_json bad pdf version 0  
426 QPDF_json top-level array 0 416 QPDF_json top-level array 0
427 -QPDF_json bad object key 0  
428 -QPDF_json trailer stream 0  
429 QPDF_json missing trailer 0 417 QPDF_json missing trailer 0
430 QPDF_json missing objects 0 418 QPDF_json missing objects 0
431 -QPDF_json ignoring in st_ignore 0  
432 -QPDF_json stream dict not dict 0  
433 QPDF_json unrecognized string value 0 419 QPDF_json unrecognized string value 0
434 QPDF_json data datafile both or neither 0 420 QPDF_json data datafile both or neither 0
435 QPDF_json stream no dict 0 421 QPDF_json stream no dict 0
@@ -438,25 +424,13 @@ QPDF_json value stream both or neither 0 @@ -438,25 +424,13 @@ QPDF_json value stream both or neither 0
438 QPDFJob need json-stream-prefix for stdout 0 424 QPDFJob need json-stream-prefix for stdout 0
439 QPDFJob write json to stdout 0 425 QPDFJob write json to stdout 0
440 QPDFJob write json to file 0 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 QPDF_json data and datafile 0 427 QPDF_json data and datafile 0
447 QPDF_json no stream data in update mode 0 428 QPDF_json no stream data in update mode 0
448 QPDF_json updating existing stream 0 429 QPDF_json updating existing stream 0
449 -QPDF_json qpdf not array 0  
450 QPDF_json more than two qpdf elements 0 430 QPDF_json more than two qpdf elements 0
451 QPDF_json missing json version 0 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 QPDFPageObjectHelper used fallback without copying 0 432 QPDFPageObjectHelper used fallback without copying 0
456 QPDF skipping cache for known unchecked object 0 433 QPDF skipping cache for known unchecked object 0
457 QPDF recover xref stream 0 434 QPDF recover xref stream 0
458 QPDFJob json over/under no file 0 435 QPDFJob json over/under no file 0
459 QPDF_Array copy 1 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