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