Commit 0132261ee06e9b94bdc011eb4dc3fcd3a403e5f3
1 parent
bac55955
Revert getOwningQPDF, and add getQPDF that returns a reference
Showing
9 changed files
with
88 additions
and
70 deletions
ChangeLog
| 1 | +2022-09-07 Jay Berkenbilt <ejb@ql.org> | |
| 2 | + | |
| 3 | + * Add QPDFObjectHandle::getQPDF(), which returns a reference, as | |
| 4 | + an alternative to QPDFObjectHandle::getOwningQPDF(). | |
| 5 | + | |
| 1 | 6 | 2022-09-06 Jay Berkenbilt <ejb@ql.org> |
| 2 | 7 | |
| 3 | 8 | * For all bounding box methods in QPDFPageObjectHelper other than | ... | ... |
include/qpdf/QPDFObjectHandle.hh
| ... | ... | @@ -661,14 +661,17 @@ class QPDFObjectHandle |
| 661 | 661 | static QPDFObjectHandle newReserved(QPDF* qpdf); |
| 662 | 662 | |
| 663 | 663 | // Provide an owning qpdf and object description. The library does |
| 664 | - // this automatically with objects that are read from the | |
| 665 | - // input PDF and with objects that are created programmatically | |
| 666 | - // and inserted into the QPDF by adding them to an array or a | |
| 667 | - // dictionary or creating a new indirect object. Most end user | |
| 664 | + // this automatically with objects that are read from the input | |
| 665 | + // PDF and with objects that are created programmatically and | |
| 666 | + // inserted into the QPDF as a new indirect object. Most end user | |
| 668 | 667 | // code will not need to call this. If an object has an owning |
| 669 | 668 | // qpdf and object description, it enables qpdf to give warnings |
| 670 | 669 | // with proper context in some cases where it would otherwise |
| 671 | - // raise exceptions. | |
| 670 | + // raise exceptions. It is okay to add objects without an | |
| 671 | + // owning_qpdf to objects that have one, but it is an error to | |
| 672 | + // have a QPDF contain objects with owning_qpdf set to something | |
| 673 | + // else. To add objects from another qpdf, use copyForeignObject | |
| 674 | + // instead. | |
| 672 | 675 | QPDF_DLL |
| 673 | 676 | void setObjectDescription( |
| 674 | 677 | QPDF* owning_qpdf, std::string const& object_description); |
| ... | ... | @@ -978,23 +981,34 @@ class QPDFObjectHandle |
| 978 | 981 | int& min_suffix, |
| 979 | 982 | std::set<std::string>* resource_names = nullptr); |
| 980 | 983 | |
| 981 | - // If this is an indirect object, return a pointer to the QPDF | |
| 982 | - // object that owns an indirect object. Direct objects are not | |
| 983 | - // owned by a QPDF. Usage notes: | |
| 984 | - // | |
| 985 | - // * When allow_nullptr is true, this method will return a null | |
| 986 | - // pointer if the object is not owned by a QPDF. Otherwise, an | |
| 987 | - // exception is thrown. | |
| 984 | + // A QPDFObjectHandle has an owning QPDF if it is associated with | |
| 985 | + // ("owned by") a specific QPDF object. Indirect objects always | |
| 986 | + // have an owning QPDF. Direct objects that are read from the | |
| 987 | + // input source will also have an owning QPDF. Programmatically | |
| 988 | + // created objects will only have one if setObjectDescription was | |
| 989 | + // called. | |
| 988 | 990 | // |
| 989 | - // * You should not retain the value returned by this method for | |
| 990 | - // longer than the lifetime of the owning QPDF object. If you | |
| 991 | - // retain a QPDFObjectHandle longer than the owning QPDF, when | |
| 992 | - // the QPDF object is destroyed, the QPDFObjectHandle will turn | |
| 993 | - // into a direct "null" object, and getOwningQPDF() called on it | |
| 994 | - // at that time will return a null pointer. | |
| 995 | - QPDF_DLL | |
| 996 | - QPDF* getOwningQPDF( | |
| 997 | - bool allow_nullptr = true, std::string const& error_msg = "") const; | |
| 991 | + // When the QPDF object that owns an object is destroyed, the | |
| 992 | + // object is changed into a null, and its owner is cleared. | |
| 993 | + // Therefore you should not retain the value of an owning QPDF | |
| 994 | + // beyond the life of the QPDF. If in doubt, ask for it each time | |
| 995 | + // you need it. | |
| 996 | + | |
| 997 | + // getOwningQPDF returns a pointer to the owning QPDF is the | |
| 998 | + // object has one. Otherwise, it returns a null pointer. Use this | |
| 999 | + // when you are able to handle the case of an object that doesn't | |
| 1000 | + // have an owning QPDF. | |
| 1001 | + QPDF_DLL | |
| 1002 | + QPDF* getOwningQPDF() const; | |
| 1003 | + // getQPDF, new in qpdf 11, returns a reference owning QPDF. If | |
| 1004 | + // there is none, it throws a runtime_error. Use this when you | |
| 1005 | + // know the object has to have an owning QPDF, such as when it's a | |
| 1006 | + // known indirect object. Since streams are always indirect | |
| 1007 | + // objects, this method can be used safely for streams. If | |
| 1008 | + // error_msg is specified, it will be used at the contents of the | |
| 1009 | + // runtime_error if there is now owner. | |
| 1010 | + QPDF_DLL | |
| 1011 | + QPDF& getQPDF(std::string const& error_msg = "") const; | |
| 998 | 1012 | |
| 999 | 1013 | // Create a shallow copy of an object as a direct object, but do not |
| 1000 | 1014 | // traverse across indirect object boundaries. That means that, | ... | ... |
libqpdf/QPDF.cc
| ... | ... | @@ -2299,14 +2299,14 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign) |
| 2299 | 2299 | throw std::logic_error( |
| 2300 | 2300 | "QPDF::copyForeign called with direct object handle"); |
| 2301 | 2301 | } |
| 2302 | - QPDF* other = foreign.getOwningQPDF(false); | |
| 2303 | - if (other == this) { | |
| 2302 | + QPDF& other = foreign.getQPDF(); | |
| 2303 | + if (&other == this) { | |
| 2304 | 2304 | QTC::TC("qpdf", "QPDF copyForeign not foreign"); |
| 2305 | 2305 | throw std::logic_error( |
| 2306 | 2306 | "QPDF::copyForeign called with object from this QPDF"); |
| 2307 | 2307 | } |
| 2308 | 2308 | |
| 2309 | - ObjCopier& obj_copier = this->m->object_copiers[other->m->unique_id]; | |
| 2309 | + ObjCopier& obj_copier = this->m->object_copiers[other.m->unique_id]; | |
| 2310 | 2310 | if (!obj_copier.visiting.empty()) { |
| 2311 | 2311 | throw std::logic_error("obj_copier.visiting is not empty" |
| 2312 | 2312 | " at the beginning of copyForeignObject"); |
| ... | ... | @@ -2490,8 +2490,8 @@ QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign) |
| 2490 | 2490 | // Copy information from the foreign stream so we can pipe its |
| 2491 | 2491 | // data later without keeping the original QPDF object around. |
| 2492 | 2492 | |
| 2493 | - QPDF* foreign_stream_qpdf = foreign.getOwningQPDF( | |
| 2494 | - false, "unable to retrieve owning qpdf from foreign stream"); | |
| 2493 | + QPDF& foreign_stream_qpdf = | |
| 2494 | + foreign.getQPDF("unable to retrieve owning qpdf from foreign stream"); | |
| 2495 | 2495 | |
| 2496 | 2496 | auto stream = QPDFObjectHandle::ObjAccessor::asStream(foreign); |
| 2497 | 2497 | if (stream == nullptr) { |
| ... | ... | @@ -2499,7 +2499,7 @@ QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign) |
| 2499 | 2499 | " stream object from foreign stream"); |
| 2500 | 2500 | } |
| 2501 | 2501 | std::shared_ptr<Buffer> stream_buffer = stream->getStreamDataBuffer(); |
| 2502 | - if ((foreign_stream_qpdf->m->immediate_copy_from) && | |
| 2502 | + if ((foreign_stream_qpdf.m->immediate_copy_from) && | |
| 2503 | 2503 | (stream_buffer == nullptr)) { |
| 2504 | 2504 | // Pull the stream data into a buffer before attempting |
| 2505 | 2505 | // the copy operation. Do it on the source stream so that |
| ... | ... | @@ -2529,8 +2529,8 @@ QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign) |
| 2529 | 2529 | dict.getKey("/DecodeParms")); |
| 2530 | 2530 | } else { |
| 2531 | 2531 | auto foreign_stream_data = std::make_shared<ForeignStreamData>( |
| 2532 | - foreign_stream_qpdf->m->encp, | |
| 2533 | - foreign_stream_qpdf->m->file, | |
| 2532 | + foreign_stream_qpdf.m->encp, | |
| 2533 | + foreign_stream_qpdf.m->file, | |
| 2534 | 2534 | foreign.getObjGen(), |
| 2535 | 2535 | stream->getOffset(), |
| 2536 | 2536 | stream->getLength(), | ... | ... |
libqpdf/QPDFFormFieldObjectHelper.cc
| ... | ... | @@ -362,12 +362,10 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) |
| 362 | 362 | setFieldAttribute("/V", value); |
| 363 | 363 | } |
| 364 | 364 | if (need_appearances) { |
| 365 | - QPDF* qpdf = this->oh.getOwningQPDF( | |
| 366 | - false, | |
| 365 | + QPDF& qpdf = this->oh.getQPDF( | |
| 367 | 366 | "QPDFFormFieldObjectHelper::setV called with need_appearances = " |
| 368 | 367 | "true on an object that is not associated with an owning QPDF"); |
| 369 | - | |
| 370 | - QPDFAcroFormDocumentHelper(*qpdf).setNeedAppearances(true); | |
| 368 | + QPDFAcroFormDocumentHelper(qpdf).setNeedAppearances(true); | |
| 371 | 369 | } |
| 372 | 370 | } |
| 373 | 371 | |
| ... | ... | @@ -881,7 +879,7 @@ QPDFFormFieldObjectHelper::generateTextAppearance( |
| 881 | 879 | if (found_font_in_dr && resources.isDictionary()) { |
| 882 | 880 | QTC::TC("qpdf", "QPDFFormFieldObjectHelper get font from /DR"); |
| 883 | 881 | if (resources.isIndirect()) { |
| 884 | - resources = resources.getOwningQPDF(false)->makeIndirectObject( | |
| 882 | + resources = resources.getQPDF().makeIndirectObject( | |
| 885 | 883 | resources.shallowCopy()); |
| 886 | 884 | AS.getDict().replaceKey("/Resources", resources); |
| 887 | 885 | } | ... | ... |
libqpdf/QPDFJob.cc
| ... | ... | @@ -2194,8 +2194,8 @@ QPDFJob::doUnderOverlayForPage( |
| 2194 | 2194 | std::map<unsigned long long, std::shared_ptr<QPDFAcroFormDocumentHelper>> |
| 2195 | 2195 | afdh; |
| 2196 | 2196 | auto make_afdh = [&](QPDFPageObjectHelper& ph) { |
| 2197 | - QPDF* q = ph.getObjectHandle().getOwningQPDF(false); | |
| 2198 | - return get_afdh_for_qpdf(afdh, q); | |
| 2197 | + QPDF& q = ph.getObjectHandle().getQPDF(); | |
| 2198 | + return get_afdh_for_qpdf(afdh, &q); | |
| 2199 | 2199 | }; |
| 2200 | 2200 | auto dest_afdh = make_afdh(dest_page); |
| 2201 | 2201 | |
| ... | ... | @@ -2635,7 +2635,7 @@ static QPDFObjectHandle |
| 2635 | 2635 | added_page(QPDF& pdf, QPDFObjectHandle page) |
| 2636 | 2636 | { |
| 2637 | 2637 | QPDFObjectHandle result = page; |
| 2638 | - if (page.getOwningQPDF(false) != &pdf) { | |
| 2638 | + if (&page.getQPDF() != &pdf) { | |
| 2639 | 2639 | // Calling copyForeignObject on an object we already copied |
| 2640 | 2640 | // will give us the already existing copy. |
| 2641 | 2641 | result = pdf.copyForeignObject(page); | ... | ... |
libqpdf/QPDFObjectHandle.cc
| ... | ... | @@ -1668,11 +1668,10 @@ QPDFObjectHandle::coalesceContentStreams() |
| 1668 | 1668 | // incorrect way. However, it can happen in a PDF file whose |
| 1669 | 1669 | // page structure is direct, which is against spec but still |
| 1670 | 1670 | // possible to hand construct, as in fuzz issue 27393. |
| 1671 | - QPDF* qpdf = getOwningQPDF( | |
| 1672 | - false, | |
| 1671 | + QPDF& qpdf = getQPDF( | |
| 1673 | 1672 | "coalesceContentStreams called on object with no associated PDF file"); |
| 1674 | 1673 | |
| 1675 | - QPDFObjectHandle new_contents = newStream(qpdf); | |
| 1674 | + QPDFObjectHandle new_contents = newStream(&qpdf); | |
| 1676 | 1675 | this->replaceKey("/Contents", new_contents); |
| 1677 | 1676 | |
| 1678 | 1677 | auto provider = std::shared_ptr<StreamDataProvider>( |
| ... | ... | @@ -2775,16 +2774,20 @@ QPDFObjectHandle::getObjGen() const |
| 2775 | 2774 | |
| 2776 | 2775 | // Indirect object accessors |
| 2777 | 2776 | QPDF* |
| 2778 | -QPDFObjectHandle::getOwningQPDF( | |
| 2779 | - bool allow_nullptr, std::string const& error_msg) const | |
| 2777 | +QPDFObjectHandle::getOwningQPDF() const | |
| 2778 | +{ | |
| 2779 | + return isInitialized() ? this->obj->getQPDF() : nullptr; | |
| 2780 | +} | |
| 2781 | + | |
| 2782 | +QPDF& | |
| 2783 | +QPDFObjectHandle::getQPDF(std::string const& error_msg) const | |
| 2780 | 2784 | { |
| 2781 | - // Will be null for direct objects | |
| 2782 | 2785 | auto result = isInitialized() ? this->obj->getQPDF() : nullptr; |
| 2783 | - if (!allow_nullptr && (result == nullptr)) { | |
| 2786 | + if (result == nullptr) { | |
| 2784 | 2787 | throw std::runtime_error( |
| 2785 | 2788 | error_msg == "" ? "attempt to use a null qpdf object" : error_msg); |
| 2786 | 2789 | } |
| 2787 | - return result; | |
| 2790 | + return *result; | |
| 2788 | 2791 | } |
| 2789 | 2792 | |
| 2790 | 2793 | void | ... | ... |
libqpdf/QPDFPageObjectHelper.cc
| ... | ... | @@ -477,8 +477,7 @@ QPDFPageObjectHelper::externalizeInlineImages(size_t min_size, bool shallow) |
| 477 | 477 | this->oh.replaceKey( |
| 478 | 478 | "/Contents", |
| 479 | 479 | QPDFObjectHandle::newStream( |
| 480 | - this->oh.getOwningQPDF(false), | |
| 481 | - b.getBufferSharedPointer())); | |
| 480 | + &this->oh.getQPDF(), b.getBufferSharedPointer())); | |
| 482 | 481 | } |
| 483 | 482 | } |
| 484 | 483 | } else { |
| ... | ... | @@ -729,12 +728,10 @@ QPDFPageObjectHelper::removeUnreferencedResources() |
| 729 | 728 | QPDFPageObjectHelper |
| 730 | 729 | QPDFPageObjectHelper::shallowCopyPage() |
| 731 | 730 | { |
| 732 | - QPDF* qpdf = this->oh.getOwningQPDF( | |
| 733 | - false, | |
| 731 | + QPDF& qpdf = this->oh.getQPDF( | |
| 734 | 732 | "QPDFPageObjectHelper::shallowCopyPage called with a direct object"); |
| 735 | - | |
| 736 | 733 | QPDFObjectHandle new_page = this->oh.shallowCopy(); |
| 737 | - return QPDFPageObjectHelper(qpdf->makeIndirectObject(new_page)); | |
| 734 | + return QPDFPageObjectHelper(qpdf.makeIndirectObject(new_page)); | |
| 738 | 735 | } |
| 739 | 736 | |
| 740 | 737 | QPDFObjectHandle::Matrix |
| ... | ... | @@ -788,11 +785,10 @@ QPDFPageObjectHelper::getMatrixForTransformations(bool invert) |
| 788 | 785 | QPDFObjectHandle |
| 789 | 786 | QPDFPageObjectHelper::getFormXObjectForPage(bool handle_transformations) |
| 790 | 787 | { |
| 791 | - QPDF* qpdf = this->oh.getOwningQPDF( | |
| 792 | - false, | |
| 788 | + QPDF& qpdf = this->oh.getQPDF( | |
| 793 | 789 | "QPDFPageObjectHelper::getFormXObjectForPage called with a direct " |
| 794 | 790 | "object"); |
| 795 | - QPDFObjectHandle result = QPDFObjectHandle::newStream(qpdf); | |
| 791 | + QPDFObjectHandle result = QPDFObjectHandle::newStream(&qpdf); | |
| 796 | 792 | QPDFObjectHandle newdict = result.getDict(); |
| 797 | 793 | newdict.replaceKey("/Type", QPDFObjectHandle::newName("/XObject")); |
| 798 | 794 | newdict.replaceKey("/Subtype", QPDFObjectHandle::newName("/Form")); |
| ... | ... | @@ -961,10 +957,8 @@ QPDFPageObjectHelper::placeFormXObject( |
| 961 | 957 | void |
| 962 | 958 | QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh) |
| 963 | 959 | { |
| 964 | - QPDF* qpdf = this->oh.getOwningQPDF( | |
| 965 | - false, | |
| 960 | + QPDF& qpdf = this->oh.getQPDF( | |
| 966 | 961 | "QPDFPageObjectHelper::flattenRotation called with a direct object"); |
| 967 | - | |
| 968 | 962 | auto rotate_oh = this->oh.getKey("/Rotate"); |
| 969 | 963 | int rotate = 0; |
| 970 | 964 | if (rotate_oh.isInteger()) { |
| ... | ... | @@ -1067,8 +1061,9 @@ QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh) |
| 1067 | 1061 | break; |
| 1068 | 1062 | } |
| 1069 | 1063 | std::string cm_str = std::string("q\n") + cm.unparse() + " cm\n"; |
| 1070 | - this->oh.addPageContents(QPDFObjectHandle::newStream(qpdf, cm_str), true); | |
| 1071 | - this->oh.addPageContents(QPDFObjectHandle::newStream(qpdf, "\nQ\n"), false); | |
| 1064 | + this->oh.addPageContents(QPDFObjectHandle::newStream(&qpdf, cm_str), true); | |
| 1065 | + this->oh.addPageContents( | |
| 1066 | + QPDFObjectHandle::newStream(&qpdf, "\nQ\n"), false); | |
| 1072 | 1067 | this->oh.removeKey("/Rotate"); |
| 1073 | 1068 | QPDFObjectHandle rotate_obj = getAttribute("/Rotate", false); |
| 1074 | 1069 | if (!rotate_obj.isNull()) { |
| ... | ... | @@ -1083,7 +1078,7 @@ QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh) |
| 1083 | 1078 | std::set<QPDFObjGen> old_fields; |
| 1084 | 1079 | std::shared_ptr<QPDFAcroFormDocumentHelper> afdhph; |
| 1085 | 1080 | if (!afdh) { |
| 1086 | - afdhph = std::make_shared<QPDFAcroFormDocumentHelper>(*qpdf); | |
| 1081 | + afdhph = std::make_shared<QPDFAcroFormDocumentHelper>(qpdf); | |
| 1087 | 1082 | afdh = afdhph.get(); |
| 1088 | 1083 | } |
| 1089 | 1084 | afdh->transformAnnotations( |
| ... | ... | @@ -1108,11 +1103,9 @@ QPDFPageObjectHelper::copyAnnotations( |
| 1108 | 1103 | return; |
| 1109 | 1104 | } |
| 1110 | 1105 | |
| 1111 | - QPDF* from_qpdf = from_page.getObjectHandle().getOwningQPDF( | |
| 1112 | - false, | |
| 1106 | + QPDF& from_qpdf = from_page.getObjectHandle().getQPDF( | |
| 1113 | 1107 | "QPDFPageObjectHelper::copyAnnotations: from page is a direct object"); |
| 1114 | - QPDF* this_qpdf = this->oh.getOwningQPDF( | |
| 1115 | - false, | |
| 1108 | + QPDF& this_qpdf = this->oh.getQPDF( | |
| 1116 | 1109 | "QPDFPageObjectHelper::copyAnnotations: this page is a direct object"); |
| 1117 | 1110 | |
| 1118 | 1111 | std::vector<QPDFObjectHandle> new_annots; |
| ... | ... | @@ -1121,19 +1114,19 @@ QPDFPageObjectHelper::copyAnnotations( |
| 1121 | 1114 | std::shared_ptr<QPDFAcroFormDocumentHelper> afdhph; |
| 1122 | 1115 | std::shared_ptr<QPDFAcroFormDocumentHelper> from_afdhph; |
| 1123 | 1116 | if (!afdh) { |
| 1124 | - afdhph = std::make_shared<QPDFAcroFormDocumentHelper>(*this_qpdf); | |
| 1117 | + afdhph = std::make_shared<QPDFAcroFormDocumentHelper>(this_qpdf); | |
| 1125 | 1118 | afdh = afdhph.get(); |
| 1126 | 1119 | } |
| 1127 | - if (this_qpdf == from_qpdf) { | |
| 1120 | + if (&this_qpdf == &from_qpdf) { | |
| 1128 | 1121 | from_afdh = afdh; |
| 1129 | 1122 | } else if (from_afdh) { |
| 1130 | - if (from_afdh->getQPDF().getUniqueId() != from_qpdf->getUniqueId()) { | |
| 1123 | + if (from_afdh->getQPDF().getUniqueId() != from_qpdf.getUniqueId()) { | |
| 1131 | 1124 | throw std::logic_error( |
| 1132 | 1125 | "QPDFAcroFormDocumentHelper::copyAnnotations: from_afdh" |
| 1133 | 1126 | " is not from the same QPDF as from_page"); |
| 1134 | 1127 | } |
| 1135 | 1128 | } else { |
| 1136 | - from_afdhph = std::make_shared<QPDFAcroFormDocumentHelper>(*from_qpdf); | |
| 1129 | + from_afdhph = std::make_shared<QPDFAcroFormDocumentHelper>(from_qpdf); | |
| 1137 | 1130 | from_afdh = from_afdhph.get(); |
| 1138 | 1131 | } |
| 1139 | 1132 | |
| ... | ... | @@ -1143,7 +1136,7 @@ QPDFPageObjectHelper::copyAnnotations( |
| 1143 | 1136 | new_fields, |
| 1144 | 1137 | old_fields, |
| 1145 | 1138 | cm, |
| 1146 | - from_qpdf, | |
| 1139 | + &from_qpdf, | |
| 1147 | 1140 | from_afdh); |
| 1148 | 1141 | afdh->addAndRenameFormFields(new_fields); |
| 1149 | 1142 | auto annots = this->oh.getKey("/Annots"); | ... | ... |
libqpdf/QPDF_pages.cc
| ... | ... | @@ -233,7 +233,7 @@ QPDF::insertPage(QPDFObjectHandle newpage, int pos) |
| 233 | 233 | newpage = makeIndirectObject(newpage); |
| 234 | 234 | } else if (newpage.getOwningQPDF() != this) { |
| 235 | 235 | QTC::TC("qpdf", "QPDF insert foreign page"); |
| 236 | - newpage.getOwningQPDF(false)->pushInheritedAttributesToPage(); | |
| 236 | + newpage.getQPDF().pushInheritedAttributesToPage(); | |
| 237 | 237 | newpage = copyForeignObject(newpage); |
| 238 | 238 | } else { |
| 239 | 239 | QTC::TC("qpdf", "QPDF insert indirect page"); | ... | ... |
manual/release-notes.rst
| ... | ... | @@ -208,6 +208,11 @@ For a detailed list of changes, please see the file |
| 208 | 208 | generally not happen for correct code, but at least the |
| 209 | 209 | situation is detectible now. |
| 210 | 210 | |
| 211 | + - The method ``QPDFObjectHandle::getQPDF`` returns a ``QPDF&`` | |
| 212 | + (rather than a ``QPDF*``) and is an alternative to | |
| 213 | + ``QPDFObjectHandle::getOwningQPDF`` for when the object is known | |
| 214 | + to have an owning ``QPDF``. | |
| 215 | + | |
| 211 | 216 | - It is now possible to test ``QPDFObjectHandle`` equality with |
| 212 | 217 | ``==`` and ``!=``. Two ``QPDFObjectHandle`` objects are equal if |
| 213 | 218 | they point to the same underlying object, meaning changes to one | ... | ... |