Commit 4ba6377f00551d34b8a77b201955e1cefd1ef3db
Committed by
GitHub
Merge pull request #1557 from m-holger/qpdf_hh
Remove implementation detail from QPDF header file
Showing
19 changed files
with
1650 additions
and
1596 deletions
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& foreign) | @@ -514,7 +533,7 @@ Objects::Foreign::Copier::copied(QPDFObjectHandle const& 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& object, qpdf_offset_t offset, std::string co | @@ -845,7 +865,7 @@ QPDF::damagedPDF(std::string const& 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& object, std::string const& message) | @@ -853,7 +873,7 @@ QPDF::damagedPDF(std::string const& object, std::string const& 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& message) | @@ -861,7 +881,7 @@ QPDF::damagedPDF(qpdf_offset_t offset, std::string const& 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& message) | @@ -869,13 +889,13 @@ QPDF::damagedPDF(std::string const& 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& pdf) | @@ -746,7 +749,7 @@ QPDFJob::doCheck(QPDF& 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& pdf) | @@ -852,7 +855,7 @@ QPDFJob::doShowPages(QPDF& 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& first, QPDF& pdf) | @@ -1040,13 +1043,14 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool& first, QPDF& 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& first, QPDF& pdf) | @@ -1105,9 +1109,10 @@ QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& 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& pdf, UnderOverlay* uo) | @@ -1852,7 +1859,7 @@ QPDFJob::validateUnderOverlay(QPDF& 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& pdf, UnderOverlay* uo) | @@ -1861,7 +1868,7 @@ QPDFJob::validateUnderOverlay(QPDF& 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& pdf) | @@ -1957,7 +1964,7 @@ QPDFJob::handleUnderOverlay(QPDF& 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& in, QPDF* a_qpdf) | @@ -2349,14 +2356,15 @@ QPDFJob::Input::initialize(Inputs& 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& w) | @@ -3001,6 +3009,8 @@ QPDFJob::setWriterOptions(QPDFWriter& 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& pdf) | @@ -3024,9 +3034,9 @@ QPDFJob::doSplitPages(QPDF& 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& qpdf) | @@ -734,15 +734,16 @@ QPDF::EncryptionParameters::initialize(QPDF& 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& qpdf) | @@ -770,14 +771,14 @@ QPDF::EncryptionParameters::initialize(QPDF& 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& str, QPDFObjGen og) | @@ -984,7 +985,7 @@ QPDF::decryptString(std::string& 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& str, QPDFObjGen og) | @@ -1017,7 +1018,8 @@ QPDF::decryptString(std::string& 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& key, JSON const& value) | @@ -460,110 +460,111 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& 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& key, JSON const& value) | @@ -574,97 +575,87 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& 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& pl, qpdf_offset_t offset, size_t length) | @@ -257,7 +543,7 @@ Lin::readHintStream(Pipeline& 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& pl, qpdf_offset_t offset, size_t length) | @@ -275,7 +561,7 @@ Lin::readHintStream(Pipeline& 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<QPDFObjectHandle> const& pages, std::map<int | @@ -718,7 +1004,7 @@ Lin::checkHSharedObject(std::vector<QPDFObjectHandle> const& pages, std::map<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& object_stream_data) | @@ -1105,12 +1391,12 @@ Lin::calculateLinearizationData(T const& 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& object_stream_data) | @@ -1128,7 +1414,7 @@ Lin::calculateLinearizationData(T const& 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& object_stream_data) | @@ -1142,15 +1428,15 @@ Lin::calculateLinearizationData(T const& 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& object_stream_data) | @@ -1181,13 +1467,13 @@ Lin::calculateLinearizationData(T const& 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& object_stream_data) | @@ -1195,7 +1481,7 @@ Lin::calculateLinearizationData(T const& 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& object_stream_data) | @@ -1231,7 +1517,7 @@ Lin::calculateLinearizationData(T const& 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& object_stream_data) | @@ -1243,7 +1529,7 @@ Lin::calculateLinearizationData(T const& 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& object_stream_data) | @@ -1288,7 +1574,7 @@ Lin::calculateLinearizationData(T const& 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& object_stream_data) | @@ -1326,7 +1612,7 @@ Lin::calculateLinearizationData(T const& 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& object_stream_data) | @@ -1337,7 +1623,7 @@ Lin::calculateLinearizationData(T const& 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& new_obj, QPDFWriter::Ob | @@ -1440,8 +1726,8 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const& 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& new_obj, QPDFWriter::Ob | @@ -1467,7 +1753,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const& 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& new_obj, QPDFWriter::Ob | @@ -1483,7 +1769,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const& 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& new_obj, QPDFWriter::Ob | @@ -1502,7 +1788,7 @@ Lin::calculateHPageOffset(QPDFWriter::NewObjTable const& 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& new_obj, QPDFWriter:: | @@ -1537,7 +1823,7 @@ Lin::calculateHSharedObject(QPDFWriter::NewObjTable const& 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& new_obj, QPDFWriter:: | @@ -1553,7 +1839,7 @@ Lin::calculateHSharedObject(QPDFWriter::NewObjTable const& 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& w) | @@ -1632,7 +1918,7 @@ Lin::writeHPageOffset(BitWriter& 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& w) | @@ -1687,7 +1973,7 @@ Lin::writeHSharedObject(BitWriter& 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& e, bool found_startxref) | @@ -208,8 +208,7 @@ Objects::reconstruct_xref(QPDFExc& 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& e, bool found_startxref) | @@ -217,9 +216,9 @@ Objects::reconstruct_xref(QPDFExc& 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& e, bool found_startxref) | @@ -253,7 +252,7 @@ Objects::reconstruct_xref(QPDFExc& 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& e, bool found_startxref) | @@ -278,7 +277,7 @@ Objects::reconstruct_xref(QPDFExc& 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& e, bool found_startxref) | @@ -313,7 +312,7 @@ Objects::reconstruct_xref(QPDFExc& 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& e, bool found_startxref) | @@ -347,7 +346,7 @@ Objects::reconstruct_xref(QPDFExc& 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& e, bool found_startxref) | @@ -368,7 +367,7 @@ Objects::reconstruct_xref(QPDFExc& 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& e, bool found_startxref) | @@ -381,23 +380,20 @@ Objects::reconstruct_xref(QPDFExc& 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& f1, int& f2, char& type) | @@ -615,7 +611,7 @@ Objects::read_bad_xrefEntry(qpdf_offset_t& f1, int& f2, char& 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& description, QPDFObjGen og) | @@ -1210,8 +1205,7 @@ Objects::readObject(std::string const& 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& description, QPDFObjGen og) | @@ -1220,7 +1214,7 @@ Objects::readObject(std::string const& 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& object, QPDFObjGen og, qpdf_offset_t offse | @@ -1241,9 +1235,9 @@ Objects::readStream(QPDFObjectHandle& 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& object, QPDFObjGen og, qpdf_offset_t offse | @@ -1251,11 +1245,11 @@ Objects::readStream(QPDFObjectHandle& 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& object, QPDFObjGen og, qpdf_off | @@ -1295,7 +1289,7 @@ Objects::validateStreamLineEnd(QPDFObjectHandle& 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& object, QPDFObjGen og, qpdf_off | @@ -1303,12 +1297,11 @@ Objects::validateStreamLineEnd(QPDFObjectHandle& 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& input, int stream_id, int obj_id) | @@ -1319,7 +1312,7 @@ Objects::readObjectInStream(is::OffsetBuffer& 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& obj, int pos, bool check_dupl | @@ -294,52 +439,51 @@ Pages::insertPageobjToPage(QPDFObjectHandle const& 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& page) | @@ -398,9 +547,15 @@ QPDF::findPage(QPDFObjectHandle& 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 |