Commit 2dc837f40f781c271ae2f0461778678a10c11e86

Authored by m-holger
Committed by GitHub
2 parents 6324d438 c5bf0fdf

Merge pull request #1554 from m-holger/foreign_stream

Refactor foreign object copying
include/qpdf/ObjectHandle.hh
... ... @@ -94,6 +94,7 @@ namespace qpdf
94 94 inline QPDFObjGen id_gen() const;
95 95 inline bool indirect() const;
96 96 inline bool null() const;
  97 + inline qpdf_offset_t offset() const;
97 98 inline QPDF* qpdf() const;
98 99 inline qpdf_object_type_e raw_type_code() const;
99 100 inline qpdf_object_type_e resolved_type_code() const;
... ...
include/qpdf/QPDF.hh
... ... @@ -742,7 +742,6 @@ class QPDF
742 742 static std::string const qpdf_version;
743 743  
744 744 class ObjCache;
745   - class ObjCopier;
746 745 class EncryptionParameters;
747 746 class ForeignStreamData;
748 747 class CopiedStreamDataProvider;
... ... @@ -775,8 +774,8 @@ class QPDF
775 774 Pipeline* pipeline,
776 775 bool suppress_warnings,
777 776 bool will_retry);
778   - bool pipeForeignStreamData(
779   - std::shared_ptr<ForeignStreamData>, Pipeline*, bool suppress_warnings, bool will_retry);
  777 + bool
  778 + pipeForeignStreamData(ForeignStreamData&, Pipeline*, bool suppress_warnings, bool will_retry);
780 779 static bool pipeStreamData(
781 780 std::shared_ptr<QPDF::EncryptionParameters> encp,
782 781 std::shared_ptr<InputSource> file,
... ... @@ -790,27 +789,6 @@ class QPDF
790 789 bool suppress_warnings,
791 790 bool will_retry);
792 791  
793   - // For QPDFWriter:
794   -
795   - std::map<QPDFObjGen, QPDFXRefEntry> const& getXRefTableInternal();
796   - // Get a list of objects that would be permitted in an object stream.
797   - template <typename T>
798   - std::vector<T> getCompressibleObjGens();
799   - std::vector<QPDFObjGen> getCompressibleObjVector();
800   - std::vector<bool> getCompressibleObjSet();
801   -
802   - // methods to support page handling
803   -
804   - void getAllPagesInternal(
805   - QPDFObjectHandle cur_pages,
806   - QPDFObjGen::set& visited,
807   - QPDFObjGen::set& seen,
808   - bool media_box,
809   - bool resources);
810   - void insertPage(QPDFObjectHandle newpage, int pos);
811   - void flattenPagesTree();
812   - void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate);
813   -
814 792 // methods to support encryption -- implemented in QPDF_encryption.cc
815 793 void initializeEncryption();
816 794 static std::string
... ... @@ -827,9 +805,6 @@ class QPDF
827 805 std::unique_ptr<Pipeline>& heap);
828 806  
829 807 // Methods to support object copying
830   - void reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top);
831   - QPDFObjectHandle
832   - replaceForeignIndirectObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top);
833 808 void copyStreamData(QPDFObjectHandle dest_stream, QPDFObjectHandle src_stream);
834 809  
835 810 struct HPageOffsetEntry;
... ...
libqpdf/QPDF.cc
... ... @@ -27,6 +27,8 @@
27 27 using namespace qpdf;
28 28 using namespace std::literals;
29 29  
  30 +using Objects = QPDF::Doc::Objects;
  31 +
30 32 // This must be a fixed value. This API returns a const reference to it, and the C API relies on its
31 33 // being static as well.
32 34 std::string const QPDF::qpdf_version(QPDF_VERSION);
... ... @@ -109,20 +111,14 @@ namespace
109 111 } // namespace
110 112  
111 113 QPDF::ForeignStreamData::ForeignStreamData(
112   - std::shared_ptr<EncryptionParameters> encp,
113   - std::shared_ptr<InputSource> file,
114   - QPDFObjGen foreign_og,
115   - qpdf_offset_t offset,
116   - size_t length,
117   - QPDFObjectHandle local_dict,
118   - bool is_root_metadata) :
119   - encp(encp),
120   - file(file),
121   - foreign_og(foreign_og),
  114 + Stream& foreign, qpdf_offset_t offset, QPDFObjectHandle local_dict) :
  115 + encp(foreign.qpdf()->m->encp),
  116 + file(foreign.qpdf()->m->file),
  117 + foreign_og(foreign.id_gen()),
122 118 offset(offset),
123   - length(length),
  119 + length(foreign.getLength()),
124 120 local_dict(local_dict),
125   - is_root_metadata(is_root_metadata)
  121 + is_root_metadata(foreign.isRootMetadata())
126 122 {
127 123 }
128 124  
... ... @@ -136,11 +132,11 @@ bool
136 132 QPDF::CopiedStreamDataProvider::provideStreamData(
137 133 QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
138 134 {
139   - std::shared_ptr<ForeignStreamData> foreign_data = foreign_stream_data[og];
  135 + auto foreign_data = foreign_stream_data.find(og);
140 136 bool result = false;
141   - if (foreign_data.get()) {
  137 + if (foreign_data != foreign_stream_data.end()) {
142 138 result = destination_qpdf.pipeForeignStreamData(
143   - foreign_data, pipeline, suppress_warnings, will_retry);
  139 + foreign_data->second, pipeline, suppress_warnings, will_retry);
144 140 QTC::TC("qpdf", "QPDF copy foreign with data", result ? 0 : 1);
145 141 } else {
146 142 auto foreign_stream = foreign_streams[og];
... ... @@ -151,20 +147,6 @@ QPDF::CopiedStreamDataProvider::provideStreamData(
151 147 return result;
152 148 }
153 149  
154   -void
155   -QPDF::CopiedStreamDataProvider::registerForeignStream(
156   - QPDFObjGen const& local_og, QPDFObjectHandle foreign_stream)
157   -{
158   - this->foreign_streams[local_og] = foreign_stream;
159   -}
160   -
161   -void
162   -QPDF::CopiedStreamDataProvider::registerForeignStream(
163   - QPDFObjGen const& local_og, std::shared_ptr<ForeignStreamData> foreign_stream)
164   -{
165   - this->foreign_stream_data[local_og] = foreign_stream;
166   -}
167   -
168 150 QPDF::StringDecrypter::StringDecrypter(QPDF* qpdf, QPDFObjGen og) :
169 151 qpdf(qpdf),
170 152 og(og)
... ... @@ -184,8 +166,8 @@ QPDF::Members::Members(QPDF&amp; qpdf) :
184 166 objects(doc.objects()),
185 167 pages(doc.pages()),
186 168 log(QPDFLogger::defaultLogger()),
187   - file(new InvalidInputSource()),
188   - encp(new EncryptionParameters)
  169 + file(std::make_shared<InvalidInputSource>()),
  170 + encp(std::make_shared<EncryptionParameters>())
189 171 {
190 172 }
191 173  
... ... @@ -491,6 +473,25 @@ QPDF::getObjectByID(int objid, int generation)
491 473 QPDFObjectHandle
492 474 QPDF::copyForeignObject(QPDFObjectHandle foreign)
493 475 {
  476 + return m->objects.foreign().copied(foreign);
  477 +}
  478 +
  479 +Objects ::Foreign::Copier&
  480 +Objects::Foreign::copier(QPDFObjectHandle const& foreign)
  481 +{
  482 + if (!foreign.isIndirect()) {
  483 + throw std::logic_error("QPDF::copyForeign called with direct object handle");
  484 + }
  485 + QPDF& other = *foreign.qpdf();
  486 + if (&other == &qpdf) {
  487 + throw std::logic_error("QPDF::copyForeign called with object from this QPDF");
  488 + }
  489 + return copiers.insert({other.getUniqueId(), {qpdf}}).first->second;
  490 +}
  491 +
  492 +QPDFObjectHandle
  493 +Objects::Foreign::Copier::copied(QPDFObjectHandle const& foreign)
  494 +{
494 495 // Here's an explanation of what's going on here.
495 496 //
496 497 // A QPDFObjectHandle that is an indirect object has an owning QPDF. The object ID and
... ... @@ -499,7 +500,7 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign)
499 500 // references to the corresponding object in the local file.
500 501 //
501 502 // To do this, we maintain mappings from foreign object IDs to local object IDs for each foreign
502   - // QPDF that we are copying from. The mapping is stored in an ObjCopier, which contains a
  503 + // QPDF that we are copying from. The mapping is stored in an Foreign::Copier, which contains a
503 504 // mapping from the foreign ObjGen to the local QPDFObjectHandle.
504 505 //
505 506 // To copy, we do a deep traversal of the foreign object with loop detection to discover all
... ... @@ -525,218 +526,185 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign)
525 526  
526 527 // Note that we explicitly allow use of copyForeignObject on page objects. It is a documented
527 528 // use case to copy pages this way if the intention is to not update the pages tree.
528   - if (!foreign.isIndirect()) {
529   - QTC::TC("qpdf", "QPDF copyForeign direct");
530   - throw std::logic_error("QPDF::copyForeign called with direct object handle");
531   - }
532   - QPDF& other = foreign.getQPDF();
533   - if (&other == this) {
534   - QTC::TC("qpdf", "QPDF copyForeign not foreign");
535   - throw std::logic_error("QPDF::copyForeign called with object from this QPDF");
536   - }
537 529  
538   - ObjCopier& obj_copier = m->object_copiers[other.m->unique_id];
539   - if (!obj_copier.visiting.empty()) {
540   - throw std::logic_error(
541   - "obj_copier.visiting is not empty at the beginning of copyForeignObject");
542   - }
  530 + util::assertion(
  531 + visiting.empty(), "obj_copier.visiting is not empty at the beginning of copyForeignObject");
543 532  
544 533 // Make sure we have an object in this file for every referenced object in the old file.
545 534 // obj_copier.object_map maps foreign QPDFObjGen to local objects. For everything new that we
546 535 // have to copy, the local object will be a reservation, unless it is a stream, in which case
547 536 // the local object will already be a stream.
548   - reserveObjects(foreign, obj_copier, true);
  537 + reserve_objects(foreign, true);
549 538  
550   - if (!obj_copier.visiting.empty()) {
551   - throw std::logic_error("obj_copier.visiting is not empty after reserving objects");
552   - }
  539 + util::assertion(visiting.empty(), "obj_copier.visiting is not empty after reserving objects");
553 540  
554 541 // Copy any new objects and replace the reservations.
555   - for (auto& to_copy: obj_copier.to_copy) {
556   - QPDFObjectHandle copy = replaceForeignIndirectObjects(to_copy, obj_copier, true);
557   - if (!to_copy.isStream()) {
558   - QPDFObjGen og(to_copy.getObjGen());
559   - replaceReserved(obj_copier.object_map[og], copy);
  542 + for (auto& oh: to_copy) {
  543 + auto copy = replace_indirect_object(oh, true);
  544 + if (!oh.isStream()) {
  545 + qpdf.replaceReserved(object_map[oh], copy);
560 546 }
561 547 }
562   - obj_copier.to_copy.clear();
  548 + to_copy.clear();
563 549  
564 550 auto og = foreign.getObjGen();
565   - if (!obj_copier.object_map.contains(og)) {
566   - warn(damagedPDF(
567   - other.getFilename() + " object " + og.unparse(' '),
568   - foreign.getParsedOffset(),
  551 + if (!object_map.contains(og)) {
  552 + qpdf.warn(qpdf.damagedPDF(
  553 + foreign.qpdf()->getFilename() + " object " + og.unparse(' '),
  554 + foreign.offset(),
569 555 "unexpected reference to /Pages object while copying foreign object; replacing with "
570 556 "null"));
571 557 return QPDFObjectHandle::newNull();
572 558 }
573   - return obj_copier.object_map[foreign.getObjGen()];
  559 + return object_map[foreign];
574 560 }
575 561  
576 562 void
577   -QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)
  563 +Objects::Foreign::Copier::reserve_objects(QPDFObjectHandle const& foreign, bool top)
578 564 {
579   - auto foreign_tc = foreign.getTypeCode();
580   - if (foreign_tc == ::ot_reserved) {
581   - throw std::logic_error("QPDF: attempting to copy a foreign reserved object");
582   - }
  565 + auto foreign_tc = foreign.type_code();
  566 + util::assertion(
  567 + foreign_tc != ::ot_reserved, "QPDF: attempting to copy a foreign reserved object");
583 568  
584 569 if (foreign.isPagesObject()) {
585 570 return;
586 571 }
587 572  
588   - if (foreign.isIndirect()) {
  573 + if (foreign.indirect()) {
589 574 QPDFObjGen foreign_og(foreign.getObjGen());
590   - if (!obj_copier.visiting.add(foreign_og)) {
  575 + if (!visiting.add(foreign_og)) {
591 576 return;
592 577 }
593   - if (obj_copier.object_map.contains(foreign_og)) {
594   - if (!(top && foreign.isPageObject() && obj_copier.object_map[foreign_og].null())) {
595   - obj_copier.visiting.erase(foreign);
  578 + if (object_map.contains(foreign_og)) {
  579 + if (!(top && foreign.isPageObject() && object_map[foreign_og].null())) {
  580 + visiting.erase(foreign);
596 581 return;
597 582 }
598 583 } else {
599   - obj_copier.object_map[foreign_og] =
600   - foreign.isStream() ? newStream() : newIndirectNull();
  584 + object_map[foreign_og] = foreign.isStream() ? qpdf.newStream() : qpdf.newIndirectNull();
601 585 if (!top && foreign.isPageObject()) {
602   - obj_copier.visiting.erase(foreign_og);
  586 + visiting.erase(foreign_og);
603 587 return;
604 588 }
605 589 }
606   - obj_copier.to_copy.emplace_back(foreign);
  590 + to_copy.emplace_back(foreign);
607 591 }
608 592  
609 593 if (foreign_tc == ::ot_array) {
610   - for (auto const& item: foreign.as_array()) {
611   - reserveObjects(item, obj_copier, false);
  594 + for (auto const& item: Array(foreign)) {
  595 + reserve_objects(item);
612 596 }
613 597 } else if (foreign_tc == ::ot_dictionary) {
614   - for (auto const& item: foreign.as_dictionary()) {
  598 + for (auto const& item: Dictionary(foreign)) {
615 599 if (!item.second.null()) {
616   - reserveObjects(item.second, obj_copier, false);
  600 + reserve_objects(item.second);
617 601 }
618 602 }
619 603 } else if (foreign_tc == ::ot_stream) {
620   - reserveObjects(foreign.getDict(), obj_copier, false);
  604 + reserve_objects(foreign.getDict());
621 605 }
622 606  
623   - obj_copier.visiting.erase(foreign);
  607 + visiting.erase(foreign);
624 608 }
625 609  
626 610 QPDFObjectHandle
627   -QPDF::replaceForeignIndirectObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)
628   -{
629   - auto foreign_tc = foreign.getTypeCode();
630   - QPDFObjectHandle result;
631   - if ((!top) && foreign.isIndirect()) {
632   - QTC::TC("qpdf", "QPDF replace indirect");
633   - auto mapping = obj_copier.object_map.find(foreign.getObjGen());
634   - if (mapping == obj_copier.object_map.end()) {
  611 +Objects::Foreign::Copier::replace_indirect_object(QPDFObjectHandle const& foreign, bool top)
  612 +{
  613 + auto foreign_tc = foreign.type_code();
  614 +
  615 + if (!top && foreign.indirect()) {
  616 + auto mapping = object_map.find(foreign.id_gen());
  617 + if (mapping == object_map.end()) {
635 618 // This case would occur if this is a reference to a Pages object that we didn't
636 619 // traverse into.
637   - QTC::TC("qpdf", "QPDF replace foreign indirect with null");
638   - result = QPDFObjectHandle::newNull();
639   - } else {
640   - result = mapping->second;
  620 + return QPDFObjectHandle::newNull();
641 621 }
642   - } else if (foreign_tc == ::ot_array) {
643   - QTC::TC("qpdf", "QPDF replace array");
644   - result = QPDFObjectHandle::newArray();
645   - for (auto const& item: foreign.as_array()) {
646   - result.appendItem(replaceForeignIndirectObjects(item, obj_copier, false));
  622 + return mapping->second;
  623 + }
  624 +
  625 + if (foreign_tc == ::ot_array) {
  626 + Array array = foreign;
  627 + std::vector<QPDFObjectHandle> result;
  628 + result.reserve(array.size());
  629 + for (auto const& item: array) {
  630 + result.emplace_back(replace_indirect_object(item));
647 631 }
648   - } else if (foreign_tc == ::ot_dictionary) {
649   - QTC::TC("qpdf", "QPDF replace dictionary");
650   - result = QPDFObjectHandle::newDictionary();
651   - for (auto const& [key, value]: foreign.as_dictionary()) {
  632 + return Array(std::move(result));
  633 + }
  634 +
  635 + if (foreign_tc == ::ot_dictionary) {
  636 + auto result = Dictionary::empty();
  637 + for (auto const& [key, value]: Dictionary(foreign)) {
652 638 if (!value.null()) {
653   - result.replaceKey(key, replaceForeignIndirectObjects(value, obj_copier, false));
  639 + result.replaceKey(key, replace_indirect_object(value));
654 640 }
655 641 }
656   - } else if (foreign_tc == ::ot_stream) {
657   - QTC::TC("qpdf", "QPDF replace stream");
658   - result = obj_copier.object_map[foreign.getObjGen()];
659   - QPDFObjectHandle dict = result.getDict();
660   - QPDFObjectHandle old_dict = foreign.getDict();
661   - for (auto const& [key, value]: old_dict.as_dictionary()) {
  642 + return result;
  643 + }
  644 +
  645 + if (foreign_tc == ::ot_stream) {
  646 + Stream stream = foreign;
  647 + Stream result = object_map[foreign];
  648 + auto dict = result.getDict();
  649 + for (auto const& [key, value]: stream.getDict()) {
662 650 if (!value.null()) {
663   - dict.replaceKey(key, replaceForeignIndirectObjects(value, obj_copier, false));
  651 + dict.replaceKey(key, replace_indirect_object(value));
664 652 }
665 653 }
666   - copyStreamData(result, foreign);
667   - } else {
668   - foreign.assertScalar();
669   - result = foreign;
670   - result.makeDirect();
671   - }
672   -
673   - if (top && (!result.isStream()) && result.isIndirect()) {
674   - throw std::logic_error("replacement for foreign object is indirect");
  654 + qpdf.copyStreamData(result, foreign);
  655 + return result;
675 656 }
676 657  
  658 + foreign.assertScalar();
  659 + auto result = foreign;
  660 + result.makeDirect();
677 661 return result;
678 662 }
679 663  
680 664 void
681   -QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign)
  665 +QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign_oh)
682 666 {
683 667 // This method was originally written for copying foreign streams, but it is used by
684   - // QPDFObjectHandle to copy streams from the same QPDF object as well.
685   -
686   - QPDFObjectHandle dict = result.getDict();
687   - QPDFObjectHandle old_dict = foreign.getDict();
688   - if (m->copied_stream_data_provider == nullptr) {
689   - m->copied_stream_data_provider = new CopiedStreamDataProvider(*this);
690   - m->copied_streams =
691   - std::shared_ptr<QPDFObjectHandle::StreamDataProvider>(m->copied_stream_data_provider);
  668 + // Stream::copy to copy streams from the same QPDF object as well.
  669 +
  670 + Dictionary dict = result.getDict();
  671 + Dictionary old_dict = foreign_oh.getDict();
  672 + if (!m->copied_stream_data_provider) {
  673 + m->copied_stream_data_provider = std::make_shared<CopiedStreamDataProvider>(*this);
692 674 }
693 675 QPDFObjGen local_og(result.getObjGen());
694 676 // Copy information from the foreign stream so we can pipe its data later without keeping the
695 677 // original QPDF object around.
696 678  
697 679 QPDF& foreign_stream_qpdf =
698   - foreign.getQPDF("unable to retrieve owning qpdf from foreign stream");
  680 + foreign_oh.getQPDF("unable to retrieve owning qpdf from foreign stream");
699 681  
700   - auto stream = foreign.as_stream();
701   - if (!stream) {
  682 + Stream foreign = foreign_oh;
  683 + if (!foreign) {
702 684 throw std::logic_error("unable to retrieve underlying stream object from foreign stream");
703 685 }
704   - std::shared_ptr<Buffer> stream_buffer = stream.getStreamDataBuffer();
705   - if ((foreign_stream_qpdf.m->immediate_copy_from) && (stream_buffer == nullptr)) {
  686 + std::shared_ptr<Buffer> stream_buffer = foreign.getStreamDataBuffer();
  687 + if (foreign_stream_qpdf.m->immediate_copy_from && !stream_buffer) {
706 688 // Pull the stream data into a buffer before attempting the copy operation. Do it on the
707 689 // source stream so that if the source stream is copied multiple times, we don't have to
708 690 // keep duplicating the memory.
709   - QTC::TC("qpdf", "QPDF immediate copy stream data");
710 691 foreign.replaceStreamData(
711   - foreign.getRawStreamData(),
712   - old_dict.getKey("/Filter"),
713   - old_dict.getKey("/DecodeParms"));
714   - stream_buffer = stream.getStreamDataBuffer();
  692 + foreign.getRawStreamData(), old_dict["/Filter"], old_dict["/DecodeParms"]);
  693 + stream_buffer = foreign.getStreamDataBuffer();
715 694 }
716   - std::shared_ptr<QPDFObjectHandle::StreamDataProvider> stream_provider =
717   - stream.getStreamDataProvider();
718   - if (stream_buffer.get()) {
719   - QTC::TC("qpdf", "QPDF copy foreign stream with buffer");
720   - result.replaceStreamData(
721   - stream_buffer, dict.getKey("/Filter"), dict.getKey("/DecodeParms"));
722   - } else if (stream_provider.get()) {
  695 + auto stream_provider = foreign.getStreamDataProvider();
  696 + if (stream_buffer) {
  697 + result.replaceStreamData(stream_buffer, dict["/Filter"], dict["/DecodeParms"]);
  698 + } else if (stream_provider) {
723 699 // In this case, the remote stream's QPDF must stay in scope.
724   - QTC::TC("qpdf", "QPDF copy foreign stream with provider");
725   - m->copied_stream_data_provider->registerForeignStream(local_og, foreign);
  700 + m->copied_stream_data_provider->registerForeignStream(local_og, foreign_oh);
726 701 result.replaceStreamData(
727   - m->copied_streams, dict.getKey("/Filter"), dict.getKey("/DecodeParms"));
  702 + m->copied_stream_data_provider, dict["/Filter"], dict["/DecodeParms"]);
728 703 } else {
729   - auto foreign_stream_data = std::make_shared<ForeignStreamData>(
730   - foreign_stream_qpdf.m->encp,
731   - foreign_stream_qpdf.m->file,
732   - foreign,
733   - foreign.getParsedOffset(),
734   - stream.getLength(),
735   - dict,
736   - stream.isRootMetadata());
  704 + auto foreign_stream_data = ForeignStreamData(foreign, foreign_oh.offset(), dict);
737 705 m->copied_stream_data_provider->registerForeignStream(local_og, foreign_stream_data);
738 706 result.replaceStreamData(
739   - m->copied_streams, dict.getKey("/Filter"), dict.getKey("/DecodeParms"));
  707 + m->copied_stream_data_provider, dict["/Filter"], dict["/DecodeParms"]);
740 708 }
741 709 }
742 710  
... ... @@ -820,11 +788,11 @@ QPDF::getRoot()
820 788 std::map<QPDFObjGen, QPDFXRefEntry>
821 789 QPDF::getXRefTable()
822 790 {
823   - return getXRefTableInternal();
  791 + return m->objects.getXRefTableInternal();
824 792 }
825 793  
826 794 std::map<QPDFObjGen, QPDFXRefEntry> const&
827   -QPDF::getXRefTableInternal()
  795 +Objects::getXRefTableInternal()
828 796 {
829 797 if (!m->parsed) {
830 798 throw std::logic_error("QPDF::getXRefTable called before parsing.");
... ... @@ -927,23 +895,20 @@ QPDF::pipeStreamData(
927 895  
928 896 bool
929 897 QPDF::pipeForeignStreamData(
930   - std::shared_ptr<ForeignStreamData> foreign,
931   - Pipeline* pipeline,
932   - bool suppress_warnings,
933   - bool will_retry)
  898 + ForeignStreamData& foreign, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
934 899 {
935   - if (foreign->encp->encrypted) {
  900 + if (foreign.encp->encrypted) {
936 901 QTC::TC("qpdf", "QPDF pipe foreign encrypted stream");
937 902 }
938 903 return pipeStreamData(
939   - foreign->encp,
940   - foreign->file,
  904 + foreign.encp,
  905 + foreign.file,
941 906 *this,
942   - foreign->foreign_og,
943   - foreign->offset,
944   - foreign->length,
945   - foreign->local_dict,
946   - foreign->is_root_metadata,
  907 + foreign.foreign_og,
  908 + foreign.offset,
  909 + foreign.length,
  910 + foreign.local_dict,
  911 + foreign.is_root_metadata,
947 912 pipeline,
948 913 suppress_warnings,
949 914 will_retry);
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -1690,7 +1690,7 @@ QPDFObjectHandle::parse(
1690 1690 qpdf_offset_t
1691 1691 QPDFObjectHandle::getParsedOffset() const
1692 1692 {
1693   - return obj ? obj->getParsedOffset() : -1;
  1693 + return offset();
1694 1694 }
1695 1695  
1696 1696 QPDFObjectHandle
... ... @@ -1935,24 +1935,6 @@ QPDFObjectHandle::makeDirect(QPDFObjGen::set&amp; visited, bool stop_at_streams)
1935 1935 visited.erase(cur_og);
1936 1936 }
1937 1937  
1938   -QPDFObjectHandle
1939   -QPDFObjectHandle::copyStream()
1940   -{
1941   - assertStream();
1942   - QPDFObjectHandle result = newStream(getOwningQPDF());
1943   - QPDFObjectHandle dict = result.getDict();
1944   - QPDFObjectHandle old_dict = getDict();
1945   - for (auto& iter: QPDFDictItems(old_dict)) {
1946   - if (iter.second.isIndirect()) {
1947   - dict.replaceKey(iter.first, iter.second);
1948   - } else {
1949   - dict.replaceKey(iter.first, iter.second.shallowCopy());
1950   - }
1951   - }
1952   - QPDF::Doc::StreamCopier::copyStreamData(getOwningQPDF(), result, *this);
1953   - return result;
1954   -}
1955   -
1956 1938 void
1957 1939 QPDFObjectHandle::makeDirect(bool allow_streams)
1958 1940 {
... ...
libqpdf/QPDFWriter.cc
... ... @@ -320,7 +320,7 @@ class QPDF::Doc::Writer
320 320 std::map<QPDFObjGen, QPDFXRefEntry> const&
321 321 getXRefTable()
322 322 {
323   - return pdf.getXRefTableInternal();
  323 + return objects.getXRefTableInternal();
324 324 }
325 325  
326 326 size_t
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -4,7 +4,6 @@
4 4 #include <qpdf/JSON_writer.hh>
5 5 #include <qpdf/Pipeline.hh>
6 6 #include <qpdf/Pipeline_private.hh>
7   -#include <qpdf/Pl_Base64.hh>
8 7 #include <qpdf/Pl_Buffer.hh>
9 8 #include <qpdf/Pl_Count.hh>
10 9 #include <qpdf/Pl_Discard.hh>
... ... @@ -45,6 +44,12 @@ class QPDF::Doc::Streams
45 44 return qpdf->pipeStreamData(
46 45 og, offset, length, dict, is_root_metadata, pipeline, suppress_warnings, will_retry);
47 46 }
  47 +
  48 + static void
  49 + copyStreamData(QPDF* qpdf, QPDFObjectHandle const& dest, QPDFObjectHandle const& src)
  50 + {
  51 + qpdf->copyStreamData(dest, src);
  52 + }
48 53 };
49 54  
50 55 namespace
... ... @@ -207,6 +212,15 @@ Stream::Stream(
207 212 setDictDescription();
208 213 }
209 214  
  215 +Stream
  216 +Stream::copy() const
  217 +{
  218 + Stream result = qpdf()->newStream();
  219 + result.stream()->stream_dict = getDict().copy();
  220 + QPDF::Doc::Streams::copyStreamData(qpdf(), result, *this);
  221 + return result;
  222 +}
  223 +
210 224 void
211 225 Stream::registerStreamFilter(
212 226 std::string const& filter_name, std::function<std::shared_ptr<QPDFStreamFilter>()> factory)
... ... @@ -350,12 +364,11 @@ Stream::getStreamData(qpdf_stream_decode_level_e decode_level)
350 364 if (!filtered) {
351 365 throw QPDFExc(
352 366 qpdf_e_unsupported,
353   - obj->getQPDF()->getFilename(),
  367 + qpdf()->getFilename(),
354 368 "",
355   - obj->getParsedOffset(),
  369 + offset(),
356 370 "getStreamData called on unfilterable stream");
357 371 }
358   - QTC::TC("qpdf", "QPDF_Stream getStreamData");
359 372 return result;
360 373 }
361 374  
... ... @@ -367,23 +380,21 @@ Stream::getRawStreamData()
367 380 if (!pipeStreamData(&buf, nullptr, 0, qpdf_dl_none, false, false)) {
368 381 throw QPDFExc(
369 382 qpdf_e_unsupported,
370   - obj->getQPDF()->getFilename(),
  383 + qpdf()->getFilename(),
371 384 "",
372   - obj->getParsedOffset(),
  385 + offset(),
373 386 "error getting raw stream data");
374 387 }
375   - QTC::TC("qpdf", "QPDF_Stream getRawStreamData");
376 388 return result;
377 389 }
378 390  
379 391 bool
380 392 Stream::isRootMetadata() const
381 393 {
382   - if (!getDict().isDictionaryOfType("/Metadata", "/XML")) {
  394 + if (!stream()->stream_dict.isDictionaryOfType("/Metadata", "/XML")) {
383 395 return false;
384 396 }
385   - auto root_metadata = qpdf()->getRoot().getKey("/Metadata");
386   - return root_metadata.isSameObjectAs(obj);
  397 + return qpdf()->getRoot()["/Metadata"].isSameObjectAs(obj);
387 398 }
388 399  
389 400 bool
... ... @@ -579,15 +590,13 @@ Stream::pipeStreamData(
579 590 s->stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(actual_length));
580 591 }
581 592 } else {
582   - if (obj->getParsedOffset() == 0) {
583   - QTC::TC("qpdf", "QPDF_Stream pipe no stream data");
  593 + if (offset() == 0) {
584 594 throw std::logic_error("pipeStreamData called for stream with no data");
585 595 }
586   - QTC::TC("qpdf", "QPDF_Stream pipe original stream data");
587 596 if (!QPDF::Doc::Streams::pipeStreamData(
588   - obj->getQPDF(),
589   - obj->getObjGen(),
590   - obj->getParsedOffset(),
  597 + qpdf(),
  598 + id_gen(),
  599 + offset(),
591 600 s->length,
592 601 s->stream_dict,
593 602 isRootMetadata(),
... ... @@ -619,6 +628,16 @@ Stream::pipeStreamData(
619 628  
620 629 void
621 630 Stream::replaceStreamData(
  631 + std::string&& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms)
  632 +{
  633 + auto s = stream();
  634 + s->stream_data = std::make_shared<Buffer>(std::move(data));
  635 + s->stream_provider = nullptr;
  636 + replaceFilterData(filter, decode_parms, s->stream_data->getSize());
  637 +}
  638 +
  639 +void
  640 +Stream::replaceStreamData(
622 641 std::shared_ptr<Buffer> data,
623 642 QPDFObjectHandle const& filter,
624 643 QPDFObjectHandle const& decode_parms)
... ... @@ -626,7 +645,7 @@ Stream::replaceStreamData(
626 645 auto s = stream();
627 646 s->stream_data = data;
628 647 s->stream_provider = nullptr;
629   - replaceFilterData(filter, decode_parms, data->getSize());
  648 + replaceFilterData(filter, decode_parms, data->size());
630 649 }
631 650  
632 651 void
... ... @@ -664,7 +683,7 @@ Stream::replaceFilterData(
664 683 void
665 684 Stream::warn(std::string const& message)
666 685 {
667   - obj->getQPDF()->warn(qpdf_e_damaged_pdf, "", obj->getParsedOffset(), message);
  686 + qpdf()->warn(qpdf_e_damaged_pdf, "", offset(), message);
668 687 }
669 688  
670 689 QPDFObjectHandle
... ... @@ -772,12 +791,8 @@ void
772 791 QPDFObjectHandle::replaceStreamData(
773 792 std::string const& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms)
774 793 {
775   - auto b = std::make_shared<Buffer>(data.length());
776   - unsigned char* bp = b->getBuffer();
777   - if (bp) {
778   - memcpy(bp, data.c_str(), data.length());
779   - }
780   - as_stream(error).replaceStreamData(b, filter, decode_parms);
  794 + std::string s(data);
  795 + as_stream(error).replaceStreamData(std::move(s), filter, decode_parms);
781 796 }
782 797  
783 798 void
... ... @@ -856,3 +871,9 @@ QPDFObjectHandle::getStreamJSON(
856 871 {
857 872 return as_stream(error).getStreamJSON(json_version, json_data, decode_level, p, data_filename);
858 873 }
  874 +
  875 +QPDFObjectHandle
  876 +QPDFObjectHandle::copyStream()
  877 +{
  878 + return as_stream(error).copy();
  879 +}
... ...
libqpdf/QPDF_json.cc
... ... @@ -424,8 +424,7 @@ QPDF::JSONReactor::replaceObject(QPDFObjectHandle&amp;&amp; replacement, JSON const&amp; val
424 424 auto og = tos.object.getObjGen();
425 425 if (replacement.isIndirect() && !(replacement.isStream() && replacement.getObjGen() == og)) {
426 426 error(
427   - replacement.getParsedOffset(),
428   - "the value of an object may not be an indirect object reference");
  427 + replacement.offset(), "the value of an object may not be an indirect object reference");
429 428 return;
430 429 }
431 430 pdf.replaceObject(og, replacement);
... ... @@ -885,7 +884,7 @@ QPDF::writeJSON(
885 884 } else {
886 885 jw << "\n },\n \"" << key;
887 886 }
888   - if (auto stream = obj.as_stream()) {
  887 + if (Stream stream = obj) {
889 888 jw << "\": {\n \"stream\": ";
890 889 if (json_stream_data == qpdf_sj_file) {
891 890 writeJSONStreamFile(
... ...
libqpdf/QPDF_objects.cc
... ... @@ -1634,7 +1634,7 @@ Objects::resolveObjectsInStream(int obj_stream_number)
1634 1634 }
1635 1635 m->resolved_object_streams.insert(obj_stream_number);
1636 1636 // Force resolution of object stream
1637   - auto obj_stream = qpdf.getObject(obj_stream_number, 0).as_stream();
  1637 + Stream obj_stream = qpdf.getObject(obj_stream_number, 0);
1638 1638 if (!obj_stream) {
1639 1639 throw qpdf.damagedPDF(
1640 1640 "object " + std::to_string(obj_stream_number) + " 0",
... ...
libqpdf/qpdf/QPDFObjectHandle_private.hh
... ... @@ -445,6 +445,23 @@ namespace qpdf
445 445 {
446 446 }
447 447  
  448 + Stream() = default;
  449 + Stream(Stream const&) = default;
  450 + Stream(Stream&&) = default;
  451 + Stream& operator=(Stream const&) = default;
  452 + Stream& operator=(Stream&&) = default;
  453 + ~Stream() = default;
  454 +
  455 + Stream(QPDFObjectHandle const& oh) :
  456 + BaseHandle(oh.type_code() == ::ot_stream ? oh : QPDFObjectHandle())
  457 + {
  458 + }
  459 +
  460 + Stream(QPDFObjectHandle&& oh) :
  461 + BaseHandle(oh.type_code() == ::ot_stream ? std::move(oh) : QPDFObjectHandle())
  462 + {
  463 + }
  464 +
448 465 Stream(
449 466 QPDF& qpdf,
450 467 QPDFObjGen og,
... ... @@ -452,10 +469,12 @@ namespace qpdf
452 469 qpdf_offset_t offset,
453 470 size_t length);
454 471  
455   - QPDFObjectHandle
  472 + Stream copy() const;
  473 +
  474 + Dictionary
456 475 getDict() const
457 476 {
458   - return stream()->stream_dict;
  477 + return {stream()->stream_dict};
459 478 }
460 479 bool
461 480 isDataModified() const
... ... @@ -501,6 +520,10 @@ namespace qpdf
501 520 std::string getStreamData(qpdf_stream_decode_level_e level);
502 521 std::string getRawStreamData();
503 522 void replaceStreamData(
  523 + std::string&& data,
  524 + QPDFObjectHandle const& filter,
  525 + QPDFObjectHandle const& decode_parms);
  526 + void replaceStreamData(
504 527 std::shared_ptr<Buffer> data,
505 528 QPDFObjectHandle const& filter,
506 529 QPDFObjectHandle const& decode_parms);
... ... @@ -657,6 +680,12 @@ namespace qpdf
657 680 return !obj || type_code() == ::ot_null;
658 681 }
659 682  
  683 + inline qpdf_offset_t
  684 + BaseHandle::offset() const
  685 + {
  686 + return obj ? obj->parsed_offset : -1;
  687 + }
  688 +
660 689 inline QPDF*
661 690 BaseHandle::qpdf() const
662 691 {
... ...
libqpdf/qpdf/QPDFObject_private.hh
... ... @@ -438,11 +438,6 @@ class QPDFObject
438 438 parsed_offset = offset;
439 439 }
440 440 }
441   - qpdf_offset_t
442   - getParsedOffset()
443   - {
444   - return parsed_offset;
445   - }
446 441 QPDF*
447 442 getQPDF()
448 443 {
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -13,10 +13,14 @@
13 13  
14 14 using namespace qpdf;
15 15  
16   -namespace qpdf::is
  16 +namespace qpdf
17 17 {
18   - class OffsetBuffer;
19   -} // namespace qpdf::is
  18 + class Stream;
  19 + namespace is
  20 + {
  21 + class OffsetBuffer;
  22 + } // namespace is
  23 +} // namespace qpdf
20 24  
21 25 class BitStream;
22 26 class BitWriter;
... ... @@ -40,14 +44,6 @@ class QPDF::ObjCache
40 44 qpdf_offset_t end_after_space{0};
41 45 };
42 46  
43   -class QPDF::ObjCopier
44   -{
45   - public:
46   - std::map<QPDFObjGen, QPDFObjectHandle> object_map;
47   - std::vector<QPDFObjectHandle> to_copy;
48   - QPDFObjGen::set visiting;
49   -};
50   -
51 47 class QPDF::EncryptionParameters
52 48 {
53 49 friend class QPDF;
... ... @@ -98,14 +94,7 @@ class QPDF::ForeignStreamData
98 94 friend class QPDF;
99 95  
100 96 public:
101   - ForeignStreamData(
102   - std::shared_ptr<EncryptionParameters> encp,
103   - std::shared_ptr<InputSource> file,
104   - QPDFObjGen foreign_og,
105   - qpdf_offset_t offset,
106   - size_t length,
107   - QPDFObjectHandle local_dict,
108   - bool is_root_metadata);
  97 + ForeignStreamData(Stream& foreign, qpdf_offset_t offset, QPDFObjectHandle local_dict);
109 98  
110 99 private:
111 100 std::shared_ptr<EncryptionParameters> encp;
... ... @@ -117,20 +106,29 @@ class QPDF::ForeignStreamData
117 106 bool is_root_metadata{false};
118 107 };
119 108  
120   -class QPDF::CopiedStreamDataProvider: public QPDFObjectHandle::StreamDataProvider
  109 +class QPDF::CopiedStreamDataProvider final: public QPDFObjectHandle::StreamDataProvider
121 110 {
122 111 public:
123 112 CopiedStreamDataProvider(QPDF& destination_qpdf);
124   - ~CopiedStreamDataProvider() override = default;
  113 + ~CopiedStreamDataProvider() final = default;
125 114 bool provideStreamData(
126   - QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry) override;
127   - void registerForeignStream(QPDFObjGen const& local_og, QPDFObjectHandle foreign_stream);
128   - void registerForeignStream(QPDFObjGen const& local_og, std::shared_ptr<ForeignStreamData>);
  115 + QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry) final;
  116 + void
  117 + registerForeignStream(QPDFObjGen const& local_og, QPDFObjectHandle foreign_stream)
  118 + {
  119 + foreign_streams.insert_or_assign(local_og, foreign_stream);
  120 + }
  121 +
  122 + void
  123 + registerForeignStream(QPDFObjGen local_og, ForeignStreamData foreign_stream)
  124 + {
  125 + foreign_stream_data.insert_or_assign(local_og, foreign_stream);
  126 + }
129 127  
130 128 private:
131 129 QPDF& destination_qpdf;
132 130 std::map<QPDFObjGen, QPDFObjectHandle> foreign_streams;
133   - std::map<QPDFObjGen, std::shared_ptr<ForeignStreamData>> foreign_stream_data;
  131 + std::map<QPDFObjGen, ForeignStreamData> foreign_stream_data;
134 132 };
135 133  
136 134 class QPDF::StringDecrypter final: public QPDFObjectHandle::StringDecrypter
... ... @@ -572,6 +570,57 @@ class QPDF::Doc
572 570 class Objects
573 571 {
574 572 public:
  573 + class Foreign
  574 + {
  575 + class Copier
  576 + {
  577 + public:
  578 + Copier(QPDF& qpdf) :
  579 + qpdf(qpdf)
  580 + {
  581 + }
  582 +
  583 + QPDFObjectHandle copied(QPDFObjectHandle const& foreign);
  584 +
  585 + private:
  586 + QPDFObjectHandle
  587 + replace_indirect_object(QPDFObjectHandle const& foreign, bool top = false);
  588 + void reserve_objects(QPDFObjectHandle const& foreign, bool top = false);
  589 +
  590 + QPDF& qpdf;
  591 + std::map<QPDFObjGen, QPDFObjectHandle> object_map;
  592 + std::vector<QPDFObjectHandle> to_copy;
  593 + QPDFObjGen::set visiting;
  594 + };
  595 +
  596 + public:
  597 + Foreign(QPDF& qpdf) :
  598 + qpdf(qpdf)
  599 + {
  600 + }
  601 +
  602 + Foreign() = delete;
  603 + Foreign(Foreign const&) = delete;
  604 + Foreign(Foreign&&) = delete;
  605 + Foreign& operator=(Foreign const&) = delete;
  606 + Foreign& operator=(Foreign&&) = delete;
  607 + ~Foreign() = default;
  608 +
  609 + // Return a local handle to the foreign object. Copy the foreign object if necessary.
  610 + QPDFObjectHandle
  611 + copied(QPDFObjectHandle const& foreign)
  612 + {
  613 + return copier(foreign).copied(foreign);
  614 + }
  615 +
  616 + private:
  617 + Copier& copier(QPDFObjectHandle const& foreign);
  618 +
  619 + QPDF& qpdf;
  620 + std::map<unsigned long long, Copier> copiers;
  621 + }; // class QPDF::Doc::Objects::Foreign
  622 +
  623 + public:
575 624 Objects() = delete;
576 625 Objects(Objects const&) = delete;
577 626 Objects(Objects&&) = delete;
... ... @@ -581,10 +630,17 @@ class QPDF::Doc
581 630  
582 631 Objects(QPDF& qpdf, QPDF::Members* m) :
583 632 qpdf(qpdf),
584   - m(m)
  633 + m(m),
  634 + foreign_(qpdf)
585 635 {
586 636 }
587 637  
  638 + Foreign&
  639 + foreign()
  640 + {
  641 + return foreign_;
  642 + }
  643 +
588 644 void parse(char const* password);
589 645 std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
590 646 void inParse(bool);
... ... @@ -607,6 +663,7 @@ class QPDF::Doc
607 663  
608 664 // For QPDFWriter:
609 665  
  666 + std::map<QPDFObjGen, QPDFXRefEntry> const& getXRefTableInternal();
610 667 // Get a list of objects that would be permitted in an object stream.
611 668 template <typename T>
612 669 std::vector<T> getCompressibleObjGens();
... ... @@ -656,9 +713,10 @@ class QPDF::Doc
656 713 bool isUnresolved(QPDFObjGen og);
657 714 void setLastObjectDescription(std::string const& description, QPDFObjGen og);
658 715  
659   - private:
660 716 QPDF& qpdf;
661 717 QPDF::Members* m;
  718 +
  719 + Foreign foreign_;
662 720 }; // class QPDF::Doc::Objects
663 721  
664 722 // This class is used to represent a PDF Pages tree.
... ... @@ -699,19 +757,6 @@ class QPDF::Doc
699 757 QPDF::Members* m;
700 758 }; // class QPDF::Doc::Pages
701 759  
702   - // StreamCopier class is restricted to QPDFObjectHandle so it can copy stream data.
703   - class StreamCopier
704   - {
705   - friend class QPDFObjectHandle;
706   -
707   - private:
708   - static void
709   - copyStreamData(QPDF* qpdf, QPDFObjectHandle const& dest, QPDFObjectHandle const& src)
710   - {
711   - qpdf->copyStreamData(dest, src);
712   - }
713   - };
714   -
715 760 Doc() = delete;
716 761 Doc(Doc const&) = delete;
717 762 Doc(Doc&&) = delete;
... ... @@ -853,10 +898,7 @@ class QPDF::Members
853 898 bool ever_pushed_inherited_attributes_to_pages{false};
854 899 bool ever_called_get_all_pages{false};
855 900 std::vector<QPDFExc> warnings;
856   - std::map<unsigned long long, ObjCopier> object_copiers;
857   - std::shared_ptr<QPDFObjectHandle::StreamDataProvider> copied_streams;
858   - // copied_stream_data_provider is owned by copied_streams
859   - CopiedStreamDataProvider* copied_stream_data_provider{nullptr};
  901 + std::shared_ptr<CopiedStreamDataProvider> copied_stream_data_provider;
860 902 bool reconstructed_xref{false};
861 903 bool in_read_xref_stream{false};
862 904 bool fixed_dangling_refs{false};
... ...
qpdf/qpdf.testcov
... ... @@ -98,16 +98,12 @@ qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0
98 98 qpdf-c called qpdf_set_static_aes_IV 0
99 99 qpdf-c called qpdf_has_error 0
100 100 qpdf-c called qpdf_get_qpdf_version 0
101   -QPDF_Stream pipe original stream data 0
102 101 QPDF_Stream pipe replaced stream data 0
103 102 QPDF_Stream provider length mismatch 0
104 103 QPDFObjectHandle newStream 0
105 104 QPDFObjectHandle newStream with data 0
106   -QPDF_Stream pipe no stream data 0
107 105 QPDFObjectHandle prepend page contents 0
108 106 QPDFObjectHandle append page contents 0
109   -QPDF_Stream getRawStreamData 0
110   -QPDF_Stream getStreamData 0
111 107 qpdf-c called qpdf_read_memory 0
112 108 QPDF stream with CRNL 0
113 109 QPDFWriter copy encrypt metadata 1
... ... @@ -128,13 +124,6 @@ QPDFObjectHandle newStream with string 0
128 124 QPDF_Stream provider length not provided 0
129 125 QPDF_Stream unknown stream length 0
130 126 QPDF replaceReserved 0
131   -QPDF copyForeign direct 0
132   -QPDF copyForeign not foreign 0
133   -QPDF replace indirect 0
134   -QPDF replace array 0
135   -QPDF replace dictionary 0
136   -QPDF replace stream 0
137   -QPDF replace foreign indirect with null 0
138 127 QPDFWriter copy use_aes 1
139 128 QPDFParser indirect without context 0
140 129 QPDFObjectHandle trailing data in parse 0
... ... @@ -256,9 +245,6 @@ QPDFJob image optimize no pipeline 0
256 245 QPDFJob image optimize no shrink 0
257 246 QPDFJob image optimize too small 0
258 247 QPDF pipe foreign encrypted stream 0
259   -QPDF copy foreign stream with provider 0
260   -QPDF copy foreign stream with buffer 0
261   -QPDF immediate copy stream data 0
262 248 QPDFJob copy same page more than once 1
263 249 QPDFPageObjectHelper bad token finding names 0
264 250 QPDFJob password mode bytes 0
... ...