2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-05-28 16:00:53 +00:00

Merge pull request #929 from m-holger/ogguard

Add new convenience class QPDFObjGen::Guard
This commit is contained in:
Jay Berkenbilt 2023-05-20 14:09:49 -04:00 committed by GitHub
commit a85635b839
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 360 additions and 345 deletions

View File

@ -1017,7 +1017,7 @@ class QPDF
public: public:
std::map<QPDFObjGen, QPDFObjectHandle> object_map; std::map<QPDFObjGen, QPDFObjectHandle> object_map;
std::vector<QPDFObjectHandle> to_copy; std::vector<QPDFObjectHandle> to_copy;
std::set<QPDFObjGen> visiting; QPDFObjGen::set visiting;
}; };
class EncryptionParameters class EncryptionParameters
@ -1252,8 +1252,8 @@ class QPDF
void getAllPagesInternal( void getAllPagesInternal(
QPDFObjectHandle cur_pages, QPDFObjectHandle cur_pages,
std::set<QPDFObjGen>& visited, QPDFObjGen::set& visited,
std::set<QPDFObjGen>& seen); QPDFObjGen::set& seen);
void insertPage(QPDFObjectHandle newpage, int pos); void insertPage(QPDFObjectHandle newpage, int pos);
void flattenPagesTree(); void flattenPagesTree();
void insertPageobjToPage( void insertPageobjToPage(
@ -1645,7 +1645,7 @@ class QPDF
ObjUser const& ou, ObjUser const& ou,
QPDFObjectHandle oh, QPDFObjectHandle oh,
std::function<int(QPDFObjectHandle&)> skip_stream_parameters, std::function<int(QPDFObjectHandle&)> skip_stream_parameters,
std::set<QPDFObjGen>& visited, QPDFObjGen::set& visited,
bool top); bool top);
void filterCompressedObjects(std::map<int, int> const& object_stream_data); void filterCompressedObjects(std::map<int, int> const& object_stream_data);

View File

@ -254,7 +254,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
QPDFObjectHandle field, QPDFObjectHandle field,
QPDFObjectHandle parent, QPDFObjectHandle parent,
int depth, int depth,
std::set<QPDFObjGen>& visited); QPDFObjGen::set& visited);
QPDFObjectHandle getOrCreateAcroForm(); QPDFObjectHandle getOrCreateAcroForm();
void adjustInheritedFields( void adjustInheritedFields(
QPDFObjectHandle obj, QPDFObjectHandle obj,

View File

@ -571,7 +571,7 @@ class QPDFJob
// JSON // JSON
void doJSON(QPDF& pdf, Pipeline*); void doJSON(QPDF& pdf, Pipeline*);
std::set<QPDFObjGen> getWantedJSONObjects(); QPDFObjGen::set getWantedJSONObjects();
void doJSONObject( void doJSONObject(
Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&); Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&);
void doJSONObjects(Pipeline* p, bool& first, QPDF& pdf); void doJSONObjects(Pipeline* p, bool& first, QPDF& pdf);

View File

@ -24,6 +24,10 @@
#include <qpdf/DLL.h> #include <qpdf/DLL.h>
#include <iostream> #include <iostream>
#include <set>
class QPDFObjectHandle;
class QPDFObjectHelper;
// This class represents an object ID and generation pair. It is // This class represents an object ID and generation pair. It is
// suitable to use as a key in a map or set. // suitable to use as a key in a map or set.
@ -31,6 +35,7 @@
class QPDFObjGen class QPDFObjGen
{ {
public: public:
// ABI: change to default.
QPDF_DLL QPDF_DLL
QPDFObjGen() : QPDFObjGen() :
obj(0), obj(0),
@ -84,12 +89,72 @@ class QPDFObjGen
QPDF_DLL QPDF_DLL
friend std::ostream& operator<<(std::ostream& os, const QPDFObjGen& og); friend std::ostream& operator<<(std::ostream& os, const QPDFObjGen& og);
// Convenience class for loop detection when processing objects.
//
// The class adds 'add' methods to a std::set<QPDFObjGen> which allows
// to test whether an QPDFObjGen is present in the set and to insert it in
// a single operation. The 'add' method is overloaded to take a QPDFObjGen,
// QPDFObjectHandle or an QPDFObjectHelper as parameter.
//
// The erase method is modified to ignore requests to erase
// QPDFObjGen(0, 0).
//
// Usage example:
//
// void process_object(QPDFObjectHandle oh, QPDFObjGen::Tracker& seen)
// {
// if (seen.add(oh)) {
// // handle first encounter of oh
// } else {
// // handle loop / subsequent encounter of oh
// }
// }
class QPDF_DLL_CLASS set: public std::set<QPDFObjGen>
{
public:
// Add 'og' to the set. Return false if 'og' is already present in
// the set. Attempts to insert QPDFObjGen(0, 0) are ignored.
QPDF_DLL
bool
add(QPDFObjGen og)
{
if (og.isIndirect()) {
if (count(og) > 0) {
return false;
}
emplace(og);
}
return true;
}
QPDF_DLL
bool add(QPDFObjectHandle const& oh);
QPDF_DLL
bool add(QPDFObjectHelper const& oh);
QPDF_DLL
void
erase(QPDFObjGen og)
{
if (og.isIndirect()) {
std::set<QPDFObjGen>::erase(og);
}
}
QPDF_DLL
void erase(QPDFObjectHandle const& oh);
QPDF_DLL
void erase(QPDFObjectHelper const& oh);
};
private: private:
// This class does not use the Members pattern to avoid a memory // This class does not use the Members pattern to avoid a memory
// allocation for every one of these. A lot of these get created // allocation for every one of these. A lot of these get created
// and destroyed. // and destroyed.
int obj; int obj{0};
int gen; int gen{0};
}; };
#endif // QPDFOBJGEN_HH #endif // QPDFOBJGEN_HH

View File

@ -1611,7 +1611,7 @@ class QPDFObjectHandle
void objectWarning(std::string const& warning); void objectWarning(std::string const& warning);
void assertType(char const* type_name, bool istype); void assertType(char const* type_name, bool istype);
inline bool dereference(); inline bool dereference();
void makeDirect(std::set<QPDFObjGen>& visited, bool stop_at_streams); void makeDirect(QPDFObjGen::set& visited, bool stop_at_streams);
void disconnect(); void disconnect();
void setParsedOffset(qpdf_offset_t offset); void setParsedOffset(qpdf_offset_t offset);
void parseContentStream_internal( void parseContentStream_internal(

View File

@ -22,13 +22,13 @@
#ifndef QPDFOUTLINEDOCUMENTHELPER_HH #ifndef QPDFOUTLINEDOCUMENTHELPER_HH
#define QPDFOUTLINEDOCUMENTHELPER_HH #define QPDFOUTLINEDOCUMENTHELPER_HH
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFDocumentHelper.hh> #include <qpdf/QPDFDocumentHelper.hh>
#include <qpdf/QPDFNameTreeObjectHelper.hh> #include <qpdf/QPDFNameTreeObjectHelper.hh>
#include <qpdf/QPDFObjGen.hh>
#include <qpdf/QPDFOutlineObjectHelper.hh> #include <qpdf/QPDFOutlineObjectHelper.hh>
#include <qpdf/QPDF.hh>
#include <map> #include <map>
#include <set>
#include <vector> #include <vector>
#include <qpdf/DLL.h> #include <qpdf/DLL.h>
@ -69,16 +69,16 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper
{ {
friend class QPDFOutlineObjectHelper; friend class QPDFOutlineObjectHelper;
// ABI: remove QPDF_DLL and pass og by value.
QPDF_DLL QPDF_DLL
static bool static bool
checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen const& og) checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen const& og)
{ {
return dh.checkSeen(og); return !dh.m->seen.add(og);
} }
}; };
private: private:
bool checkSeen(QPDFObjGen const& og);
void initializeByPage(); void initializeByPage();
class Members class Members
@ -94,7 +94,7 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper
Members(Members const&) = delete; Members(Members const&) = delete;
std::vector<QPDFOutlineObjectHelper> outlines; std::vector<QPDFOutlineObjectHelper> outlines;
std::set<QPDFObjGen> seen; QPDFObjGen::set seen;
QPDFObjectHandle dest_dict; QPDFObjectHandle dest_dict;
std::shared_ptr<QPDFNameTreeObjectHelper> names_dest; std::shared_ptr<QPDFNameTreeObjectHelper> names_dest;
std::map<QPDFObjGen, std::vector<QPDFOutlineObjectHelper>> by_page; std::map<QPDFObjGen, std::vector<QPDFOutlineObjectHelper>> by_page;

View File

@ -638,26 +638,21 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first, bool allow_empty)
auto opath = this->path; auto opath = this->path;
bool failed = false; bool failed = false;
std::set<QPDFObjGen> seen; QPDFObjGen::set seen;
for (auto i: this->path) { for (auto i: this->path) {
if (i.node.isIndirect()) { seen.add(i.node);
seen.insert(i.node.getObjGen());
}
} }
while (!failed) { while (!failed) {
if (node.isIndirect()) { if (!seen.add(node)) {
auto og = node.getObjGen(); QTC::TC("qpdf", "NNTree deepen: loop");
if (seen.count(og)) { warn(
QTC::TC("qpdf", "NNTree deepen: loop"); impl.qpdf,
warn( node,
impl.qpdf, "loop detected while traversing name/number tree");
node, failed = true;
"loop detected while traversing name/number tree"); break;
failed = true;
break;
}
seen.insert(og);
} }
if (!node.isDictionary()) { if (!node.isDictionary()) {
QTC::TC("qpdf", "NNTree node is not a dictionary"); QTC::TC("qpdf", "NNTree node is not a dictionary");
warn( warn(
@ -928,17 +923,15 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found)
} }
} }
std::set<QPDFObjGen> seen; QPDFObjGen::set seen;
auto node = this->oh; auto node = this->oh;
iterator result(*this); iterator result(*this);
while (true) { while (true) {
auto og = node.getObjGen(); if (!seen.add(node)) {
if (seen.count(og)) {
QTC::TC("qpdf", "NNTree loop in find"); QTC::TC("qpdf", "NNTree loop in find");
error(qpdf, node, "loop detected in find"); error(qpdf, node, "loop detected in find");
} }
seen.insert(og);
auto kids = node.getKey("/Kids"); auto kids = node.getKey("/Kids");
int nkids = kids.isArray() ? kids.getArrayNItems() : 0; int nkids = kids.isArray() ? kids.getArrayNItems() : 0;

View File

@ -2176,7 +2176,8 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign)
void void
QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)
{ {
if (foreign.isReserved()) { auto foreign_tc = foreign.getTypeCode();
if (foreign_tc == ::ot_reserved) {
throw std::logic_error( throw std::logic_error(
"QPDF: attempting to copy a foreign reserved object"); "QPDF: attempting to copy a foreign reserved object");
} }
@ -2193,70 +2194,62 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)
if (foreign.isIndirect()) { if (foreign.isIndirect()) {
QPDFObjGen foreign_og(foreign.getObjGen()); QPDFObjGen foreign_og(foreign.getObjGen());
if (obj_copier.visiting.find(foreign_og) != obj_copier.visiting.end()) { if (obj_copier.object_map.count(foreign_og) > 0) {
QTC::TC("qpdf", "QPDF loop reserving objects"); QTC::TC("qpdf", "QPDF already reserved object");
if (obj_copier.visiting.count(foreign_og)) {
QTC::TC("qpdf", "QPDF loop reserving objects");
}
return; return;
} }
if (obj_copier.object_map.find(foreign_og) != if (!obj_copier.visiting.add(foreign_og)) {
obj_copier.object_map.end()) {
QTC::TC("qpdf", "QPDF already reserved object");
return; return;
} }
QTC::TC("qpdf", "QPDF copy indirect"); QTC::TC("qpdf", "QPDF copy indirect");
obj_copier.visiting.insert(foreign_og); if (obj_copier.object_map.count(foreign_og) == 0) {
auto mapping = obj_copier.object_map.find(foreign_og);
if (mapping == obj_copier.object_map.end()) {
obj_copier.to_copy.push_back(foreign); obj_copier.to_copy.push_back(foreign);
QPDFObjectHandle reservation; obj_copier.object_map[foreign_og] = foreign.isStream()
if (foreign.isStream()) { ? newStream()
reservation = newStream(); : QPDFObjectHandle::newReserved(this);
} else {
reservation = QPDFObjectHandle::newReserved(this);
}
obj_copier.object_map[foreign_og] = reservation;
} }
} }
if (foreign.isArray()) { if (foreign_tc == ::ot_array) {
QTC::TC("qpdf", "QPDF reserve array"); QTC::TC("qpdf", "QPDF reserve array");
int n = foreign.getArrayNItems(); int n = foreign.getArrayNItems();
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
reserveObjects(foreign.getArrayItem(i), obj_copier, false); reserveObjects(foreign.getArrayItem(i), obj_copier, false);
} }
} else if (foreign.isDictionary()) { } else if (foreign_tc == ::ot_dictionary) {
QTC::TC("qpdf", "QPDF reserve dictionary"); QTC::TC("qpdf", "QPDF reserve dictionary");
for (auto const& key: foreign.getKeys()) { for (auto const& key: foreign.getKeys()) {
reserveObjects(foreign.getKey(key), obj_copier, false); reserveObjects(foreign.getKey(key), obj_copier, false);
} }
} else if (foreign.isStream()) { } else if (foreign_tc == ::ot_stream) {
QTC::TC("qpdf", "QPDF reserve stream"); QTC::TC("qpdf", "QPDF reserve stream");
reserveObjects(foreign.getDict(), obj_copier, false); reserveObjects(foreign.getDict(), obj_copier, false);
} }
if (foreign.isIndirect()) { obj_copier.visiting.erase(foreign);
QPDFObjGen foreign_og(foreign.getObjGen());
obj_copier.visiting.erase(foreign_og);
}
} }
QPDFObjectHandle QPDFObjectHandle
QPDF::replaceForeignIndirectObjects( QPDF::replaceForeignIndirectObjects(
QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)
{ {
auto foreign_tc = foreign.getTypeCode();
QPDFObjectHandle result; QPDFObjectHandle result;
if ((!top) && foreign.isIndirect()) { if ((!top) && foreign.isIndirect()) {
QTC::TC("qpdf", "QPDF replace indirect"); QTC::TC("qpdf", "QPDF replace indirect");
QPDFObjGen foreign_og(foreign.getObjGen()); auto mapping = obj_copier.object_map.find(foreign.getObjGen());
auto mapping = obj_copier.object_map.find(foreign_og);
if (mapping == obj_copier.object_map.end()) { if (mapping == obj_copier.object_map.end()) {
// This case would occur if this is a reference to a Page // This case would occur if this is a reference to a Page
// or Pages object that we didn't traverse into. // or Pages object that we didn't traverse into.
QTC::TC("qpdf", "QPDF replace foreign indirect with null"); QTC::TC("qpdf", "QPDF replace foreign indirect with null");
result = QPDFObjectHandle::newNull(); result = QPDFObjectHandle::newNull();
} else { } else {
result = obj_copier.object_map[foreign_og]; result = mapping->second;
} }
} else if (foreign.isArray()) { } else if (foreign_tc == ::ot_array) {
QTC::TC("qpdf", "QPDF replace array"); QTC::TC("qpdf", "QPDF replace array");
result = QPDFObjectHandle::newArray(); result = QPDFObjectHandle::newArray();
int n = foreign.getArrayNItems(); int n = foreign.getArrayNItems();
@ -2266,7 +2259,7 @@ QPDF::replaceForeignIndirectObjects(
replaceForeignIndirectObjects( replaceForeignIndirectObjects(
foreign.getArrayItem(i), obj_copier, false)); foreign.getArrayItem(i), obj_copier, false));
} }
} else if (foreign.isDictionary()) { } else if (foreign_tc == ::ot_dictionary) {
QTC::TC("qpdf", "QPDF replace dictionary"); QTC::TC("qpdf", "QPDF replace dictionary");
result = QPDFObjectHandle::newDictionary(); result = QPDFObjectHandle::newDictionary();
std::set<std::string> keys = foreign.getKeys(); std::set<std::string> keys = foreign.getKeys();
@ -2276,10 +2269,9 @@ QPDF::replaceForeignIndirectObjects(
replaceForeignIndirectObjects( replaceForeignIndirectObjects(
foreign.getKey(iter), obj_copier, false)); foreign.getKey(iter), obj_copier, false));
} }
} else if (foreign.isStream()) { } else if (foreign_tc == ::ot_stream) {
QTC::TC("qpdf", "QPDF replace stream"); QTC::TC("qpdf", "QPDF replace stream");
QPDFObjGen foreign_og(foreign.getObjGen()); result = obj_copier.object_map[foreign.getObjGen()];
result = obj_copier.object_map[foreign_og];
result.assertStream(); result.assertStream();
QPDFObjectHandle dict = result.getDict(); QPDFObjectHandle dict = result.getDict();
QPDFObjectHandle old_dict = foreign.getDict(); QPDFObjectHandle old_dict = foreign.getDict();
@ -2511,7 +2503,7 @@ QPDF::getCompressibleObjGens()
QPDFObjectHandle encryption_dict = this->m->trailer.getKey("/Encrypt"); QPDFObjectHandle encryption_dict = this->m->trailer.getKey("/Encrypt");
QPDFObjGen encryption_dict_og = encryption_dict.getObjGen(); QPDFObjGen encryption_dict_og = encryption_dict.getObjGen();
std::set<QPDFObjGen> visited; QPDFObjGen::set visited;
std::list<QPDFObjectHandle> queue; std::list<QPDFObjectHandle> queue;
queue.push_front(this->m->trailer); queue.push_front(this->m->trailer);
std::vector<QPDFObjGen> result; std::vector<QPDFObjGen> result;
@ -2520,7 +2512,7 @@ QPDF::getCompressibleObjGens()
queue.pop_front(); queue.pop_front();
if (obj.isIndirect()) { if (obj.isIndirect()) {
QPDFObjGen og = obj.getObjGen(); QPDFObjGen og = obj.getObjGen();
if (visited.count(og)) { if (!visited.add(og)) {
QTC::TC("qpdf", "QPDF loop detected traversing objects"); QTC::TC("qpdf", "QPDF loop detected traversing objects");
continue; continue;
} }
@ -2532,7 +2524,6 @@ QPDF::getCompressibleObjGens()
obj.hasKey("/Contents")))) { obj.hasKey("/Contents")))) {
result.push_back(og); result.push_back(og);
} }
visited.insert(og);
} }
if (obj.isStream()) { if (obj.isStream()) {
QPDFObjectHandle dict = obj.getDict(); QPDFObjectHandle dict = obj.getDict();

View File

@ -57,7 +57,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
"/Fields", QPDFObjectHandle::newArray()); "/Fields", QPDFObjectHandle::newArray());
} }
fields.appendItem(ff.getObjectHandle()); fields.appendItem(ff.getObjectHandle());
std::set<QPDFObjGen> visited; QPDFObjGen::set visited;
traverseField( traverseField(
ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited); ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited);
} }
@ -68,53 +68,48 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(
{ {
analyze(); analyze();
std::map<std::string, std::string> renames; std::map<std::string, std::string> renames;
std::list<QPDFObjectHandle> queue; QPDFObjGen::set seen;
queue.insert(queue.begin(), fields.begin(), fields.end()); for (std::list<QPDFObjectHandle> queue{fields.begin(), fields.end()};
std::set<QPDFObjGen> seen; !queue.empty();
while (!queue.empty()) { queue.pop_front()) {
QPDFObjectHandle obj = queue.front(); auto& obj = queue.front();
queue.pop_front(); if (seen.add(obj)) {
auto og = obj.getObjGen(); auto kids = obj.getKey("/Kids");
if (seen.count(og)) { if (kids.isArray()) {
// loop for (auto kid: kids.aitems()) {
continue; queue.push_back(kid);
}
seen.insert(og);
auto kids = obj.getKey("/Kids");
if (kids.isArray()) {
for (auto kid: kids.aitems()) {
queue.push_back(kid);
}
}
if (obj.hasKey("/T")) {
// Find something we can append to the partial name that
// makes the fully qualified name unique. When we find
// something, reuse the same suffix for all fields in this
// group with the same name. We can only change the name
// of fields that have /T, and this field's /T is always
// at the end of the fully qualified name, appending to /T
// has the effect of appending the same thing to the fully
// qualified name.
std::string old_name =
QPDFFormFieldObjectHelper(obj).getFullyQualifiedName();
if (renames.count(old_name) == 0) {
std::string new_name = old_name;
int suffix = 0;
std::string append;
while (!getFieldsWithQualifiedName(new_name).empty()) {
++suffix;
append = "+" + std::to_string(suffix);
new_name = old_name + append;
} }
renames[old_name] = append;
} }
std::string append = renames[old_name];
if (!append.empty()) { if (obj.hasKey("/T")) {
obj.replaceKey( // Find something we can append to the partial name that
"/T", // makes the fully qualified name unique. When we find
QPDFObjectHandle::newUnicodeString( // something, reuse the same suffix for all fields in this
obj.getKey("/T").getUTF8Value() + append)); // group with the same name. We can only change the name
// of fields that have /T, and this field's /T is always
// at the end of the fully qualified name, appending to /T
// has the effect of appending the same thing to the fully
// qualified name.
std::string old_name =
QPDFFormFieldObjectHelper(obj).getFullyQualifiedName();
if (renames.count(old_name) == 0) {
std::string new_name = old_name;
int suffix = 0;
std::string append;
while (!getFieldsWithQualifiedName(new_name).empty()) {
++suffix;
append = "+" + std::to_string(suffix);
new_name = old_name + append;
}
renames[old_name] = append;
}
std::string append = renames[old_name];
if (!append.empty()) {
obj.replaceKey(
"/T",
QPDFObjectHandle::newUnicodeString(
obj.getKey("/T").getUTF8Value() + append));
}
} }
} }
} }
@ -172,7 +167,7 @@ QPDFAcroFormDocumentHelper::setFormFieldName(
QPDFFormFieldObjectHelper ff, std::string const& name) QPDFFormFieldObjectHelper ff, std::string const& name)
{ {
ff.setFieldAttribute("/T", name); ff.setFieldAttribute("/T", name);
std::set<QPDFObjGen> visited; QPDFObjGen::set visited;
auto ff_oh = ff.getObjectHandle(); auto ff_oh = ff.getObjectHandle();
traverseField(ff_oh, ff_oh.getKey("/Parent"), 0, visited); traverseField(ff_oh, ff_oh.getKey("/Parent"), 0, visited);
} }
@ -193,12 +188,11 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name)
{ {
analyze(); analyze();
// Keep from creating an empty entry // Keep from creating an empty entry
std::set<QPDFObjGen> result;
auto iter = this->m->name_to_fields.find(name); auto iter = this->m->name_to_fields.find(name);
if (iter != this->m->name_to_fields.end()) { if (iter != this->m->name_to_fields.end()) {
result = iter->second; return iter->second;
} }
return result; return {};
} }
std::vector<QPDFAnnotationObjectHelper> std::vector<QPDFAnnotationObjectHelper>
@ -223,18 +217,12 @@ std::vector<QPDFFormFieldObjectHelper>
QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph)
{ {
analyze(); analyze();
std::set<QPDFObjGen> added; QPDFObjGen::set todo;
std::vector<QPDFFormFieldObjectHelper> result; std::vector<QPDFFormFieldObjectHelper> result;
auto widget_annotations = getWidgetAnnotationsForPage(ph); for (auto& annot: getWidgetAnnotationsForPage(ph)) {
for (auto annot: widget_annotations) { auto field = getFieldForAnnotation(annot).getTopLevelField();
auto field = getFieldForAnnotation(annot); if (todo.add(field) && field.getObjectHandle().isDictionary()) {
field = field.getTopLevelField(); result.push_back(field);
auto og = field.getObjectHandle().getObjGen();
if (!added.count(og)) {
added.insert(og);
if (field.getObjectHandle().isDictionary()) {
result.push_back(field);
}
} }
} }
return result; return result;
@ -278,7 +266,7 @@ QPDFAcroFormDocumentHelper::analyze()
// Traverse /AcroForm to find annotations and map them // Traverse /AcroForm to find annotations and map them
// bidirectionally to fields. // bidirectionally to fields.
std::set<QPDFObjGen> visited; QPDFObjGen::set visited;
int nfields = fields.getArrayNItems(); int nfields = fields.getArrayNItems();
QPDFObjectHandle null(QPDFObjectHandle::newNull()); QPDFObjectHandle null(QPDFObjectHandle::newNull());
for (int i = 0; i < nfields; ++i) { for (int i = 0; i < nfields; ++i) {
@ -324,7 +312,7 @@ QPDFAcroFormDocumentHelper::traverseField(
QPDFObjectHandle field, QPDFObjectHandle field,
QPDFObjectHandle parent, QPDFObjectHandle parent,
int depth, int depth,
std::set<QPDFObjGen>& visited) QPDFObjGen::set& visited)
{ {
if (depth > 100) { if (depth > 100) {
// Arbitrarily cut off recursion at a fixed depth to avoid // Arbitrarily cut off recursion at a fixed depth to avoid
@ -346,12 +334,11 @@ QPDFAcroFormDocumentHelper::traverseField(
return; return;
} }
QPDFObjGen og(field.getObjGen()); QPDFObjGen og(field.getObjGen());
if (visited.count(og) != 0) { if (!visited.add(og)) {
QTC::TC("qpdf", "QPDFAcroFormDocumentHelper loop"); QTC::TC("qpdf", "QPDFAcroFormDocumentHelper loop");
field.warnIfPossible("loop detected while traversing /AcroForm"); field.warnIfPossible("loop detected while traversing /AcroForm");
return; return;
} }
visited.insert(og);
// A dictionary encountered while traversing the /AcroForm field // A dictionary encountered while traversing the /AcroForm field
// may be a form field, an annotation, or the merger of the two. A // may be a form field, an annotation, or the merger of the two. A
@ -888,7 +875,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
// Now do the actual copies. // Now do the actual copies.
std::set<QPDFObjGen> added_new_fields; QPDFObjGen::set added_new_fields;
for (auto annot: old_annots.aitems()) { for (auto annot: old_annots.aitems()) {
if (annot.isStream()) { if (annot.isStream()) {
annot.warnIfPossible("ignoring annotation that's a stream"); annot.warnIfPossible("ignoring annotation that's a stream");
@ -970,73 +957,68 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
// Traverse the field, copying kids, and preserving // Traverse the field, copying kids, and preserving
// integrity. // integrity.
std::list<QPDFObjectHandle> queue; std::list<QPDFObjectHandle> queue;
QPDFObjGen::set seen;
if (maybe_copy_object(top_field)) { if (maybe_copy_object(top_field)) {
queue.push_back(top_field); queue.push_back(top_field);
} }
std::set<QPDFObjGen> seen; for (; !queue.empty(); queue.pop_front()) {
while (!queue.empty()) { auto& obj = queue.front();
QPDFObjectHandle obj = queue.front(); if (seen.add(obj)) {
queue.pop_front(); auto parent = obj.getKey("/Parent");
auto orig_og = obj.getObjGen(); if (parent.isIndirect()) {
if (seen.count(orig_og)) { auto parent_og = parent.getObjGen();
// loop if (orig_to_copy.count(parent_og)) {
break; obj.replaceKey("/Parent", orig_to_copy[parent_og]);
} } else {
seen.insert(orig_og); parent.warnIfPossible(
auto parent = obj.getKey("/Parent"); "while traversing field " +
if (parent.isIndirect()) { obj.getObjGen().unparse(',') +
auto parent_og = parent.getObjGen(); ", found parent (" + parent_og.unparse(',') +
if (orig_to_copy.count(parent_og)) { ") that had not been seen, indicating likely"
obj.replaceKey("/Parent", orig_to_copy[parent_og]); " invalid field structure");
} else {
parent.warnIfPossible(
"while traversing field " +
obj.getObjGen().unparse(',') + ", found parent (" +
parent_og.unparse(',') +
") that had not been seen, indicating likely"
" invalid field structure");
}
}
auto kids = obj.getKey("/Kids");
if (kids.isArray()) {
for (int i = 0; i < kids.getArrayNItems(); ++i) {
auto kid = kids.getArrayItem(i);
if (maybe_copy_object(kid)) {
kids.setArrayItem(i, kid);
queue.push_back(kid);
} }
} }
} auto kids = obj.getKey("/Kids");
if (kids.isArray()) {
if (override_da || override_q) { for (int i = 0; i < kids.getArrayNItems(); ++i) {
adjustInheritedFields( auto kid = kids.getArrayItem(i);
obj, if (maybe_copy_object(kid)) {
override_da, kids.setArrayItem(i, kid);
from_default_da, queue.push_back(kid);
override_q, }
from_default_q); }
} }
if (foreign) {
// Lazily initialize our /DR and the conflict map. if (override_da || override_q) {
init_dr_map(); adjustInheritedFields(
// The spec doesn't say anything about /DR on the obj,
// field, but lots of writers put one there, and override_da,
// it is frequently the same as the document-level from_default_da,
// /DR. To avoid having the field's /DR point to override_q,
// information that we are not maintaining, just from_default_q);
// reset it to that if it exists. Empirical }
// evidence suggests that many readers, including if (foreign) {
// Acrobat, Adobe Acrobat Reader, chrome, firefox, // Lazily initialize our /DR and the conflict map.
// the mac Preview application, and several of the init_dr_map();
// free readers on Linux all ignore /DR at the // The spec doesn't say anything about /DR on the
// field level. // field, but lots of writers put one there, and
if (obj.hasKey("/DR")) { // it is frequently the same as the document-level
obj.replaceKey("/DR", dr); // /DR. To avoid having the field's /DR point to
// information that we are not maintaining, just
// reset it to that if it exists. Empirical
// evidence suggests that many readers, including
// Acrobat, Adobe Acrobat Reader, chrome, firefox,
// the mac Preview application, and several of the
// free readers on Linux all ignore /DR at the
// field level.
if (obj.hasKey("/DR")) {
obj.replaceKey("/DR", dr);
}
}
if (foreign && obj.getKey("/DA").isString() &&
(!dr_map.empty())) {
adjustDefaultAppearances(obj, dr_map);
} }
}
if (foreign && obj.getKey("/DA").isString() &&
(!dr_map.empty())) {
adjustDefaultAppearances(obj, dr_map);
} }
} }
@ -1064,9 +1046,8 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
maybe_copy_object(annot); maybe_copy_object(annot);
// Now we have copies, so we can safely mutate. // Now we have copies, so we can safely mutate.
if (have_field && !added_new_fields.count(top_field.getObjGen())) { if (have_field && added_new_fields.add(top_field)) {
new_fields.push_back(top_field); new_fields.push_back(top_field);
added_new_fields.insert(top_field.getObjGen());
} }
new_annots.push_back(annot); new_annots.push_back(annot);

View File

@ -36,20 +36,14 @@ QPDFFormFieldObjectHelper
QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different)
{ {
auto top_field = this->oh; auto top_field = this->oh;
std::set<QPDFObjGen> seen; QPDFObjGen::set seen;
while (top_field.isDictionary() && while (seen.add(top_field) && !top_field.getKeyIfDict("/Parent").isNull()) {
(!top_field.getKey("/Parent").isNull())) {
top_field = top_field.getKey("/Parent"); top_field = top_field.getKey("/Parent");
if (is_different) { if (is_different) {
*is_different = true; *is_different = true;
} }
auto og = top_field.getObjGen();
if (seen.count(og)) {
break;
}
seen.insert(og);
} }
return QPDFFormFieldObjectHelper(top_field); return {top_field};
} }
QPDFObjectHandle QPDFObjectHandle
@ -76,17 +70,17 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name)
return QPDFObjectHandle::newNull(); return QPDFObjectHandle::newNull();
} }
QPDFObjectHandle result(node.getKey(name)); QPDFObjectHandle result(node.getKey(name));
std::set<QPDFObjGen> seen; if (result.isNull()) {
while (result.isNull() && node.hasKey("/Parent")) { QPDFObjGen::set seen;
seen.insert(node.getObjGen()); while (seen.add(node) && node.hasKey("/Parent")) {
node = node.getKey("/Parent"); node = node.getKey("/Parent");
if (seen.count(node.getObjGen())) { result = node.getKey(name);
break; if (!result.isNull()) {
} QTC::TC(
result = node.getKey(name); "qpdf",
if (!result.isNull()) { "QPDFFormFieldObjectHelper non-trivial inheritance");
QTC::TC( return result;
"qpdf", "QPDFFormFieldObjectHelper non-trivial inheritance"); }
} }
} }
return result; return result;
@ -127,8 +121,8 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName()
{ {
std::string result; std::string result;
QPDFObjectHandle node = this->oh; QPDFObjectHandle node = this->oh;
std::set<QPDFObjGen> seen; QPDFObjGen::set seen;
while ((!node.isNull()) && (seen.count(node.getObjGen()) == 0)) { while (!node.isNull() && seen.add(node)) {
if (node.getKey("/T").isString()) { if (node.getKey("/T").isString()) {
if (!result.empty()) { if (!result.empty()) {
QTC::TC( QTC::TC(
@ -138,7 +132,6 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName()
} }
result = node.getKey("/T").getUTF8Value() + result; result = node.getKey("/T").getUTF8Value() + result;
} }
seen.insert(node.getObjGen());
node = node.getKey("/Parent"); node = node.getKey("/Parent");
} }
return result; return result;

View File

@ -1001,18 +1001,16 @@ QPDFJob::parse_object_id(
} }
} }
std::set<QPDFObjGen> QPDFObjGen::set
QPDFJob::getWantedJSONObjects() QPDFJob::getWantedJSONObjects()
{ {
std::set<QPDFObjGen> wanted_og; QPDFObjGen::set wanted_og;
for (auto const& iter: m->json_objects) { for (auto const& iter: m->json_objects) {
bool trailer; bool trailer;
int obj = 0; int obj = 0;
int gen = 0; int gen = 0;
parse_object_id(iter, trailer, obj, gen); parse_object_id(iter, trailer, obj, gen);
if (obj) { wanted_og.add(QPDFObjGen(obj, gen));
wanted_og.insert(QPDFObjGen(obj, gen));
}
} }
return wanted_og; return wanted_og;
} }
@ -1045,7 +1043,7 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf)
bool first_object = true; bool first_object = true;
JSON::writeDictionaryOpen(p, first_object, 1); JSON::writeDictionaryOpen(p, first_object, 1);
bool all_objects = m->json_objects.empty(); bool all_objects = m->json_objects.empty();
std::set<QPDFObjGen> wanted_og = getWantedJSONObjects(); auto wanted_og = getWantedJSONObjects();
for (auto& obj: pdf.getAllObjects()) { for (auto& obj: pdf.getAllObjects()) {
std::string key = obj.unparse(); std::string key = obj.unparse();
if (this->m->json_version > 1) { if (this->m->json_version > 1) {
@ -1065,11 +1063,8 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf)
if (this->m->json_objects.count("trailer")) { if (this->m->json_objects.count("trailer")) {
json_objects.insert("trailer"); json_objects.insert("trailer");
} }
auto wanted = getWantedJSONObjects(); for (auto og: getWantedJSONObjects()) {
for (auto const& og: wanted) { json_objects.emplace("obj:" + og.unparse(' ') + " R");
std::ostringstream s;
s << "obj:" << og.unparse(' ') << " R";
json_objects.insert(s.str());
} }
pdf.writeJSON( pdf.writeJSON(
this->m->json_version, this->m->json_version,
@ -1090,7 +1085,7 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool& first, QPDF& pdf)
bool first_object = true; bool first_object = true;
JSON::writeDictionaryOpen(p, first_object, 1); JSON::writeDictionaryOpen(p, first_object, 1);
bool all_objects = m->json_objects.empty(); bool all_objects = m->json_objects.empty();
std::set<QPDFObjGen> wanted_og = getWantedJSONObjects(); auto wanted_og = getWantedJSONObjects();
for (auto& obj: pdf.getAllObjects()) { for (auto& obj: pdf.getAllObjects()) {
if (all_objects || wanted_og.count(obj.getObjGen())) { if (all_objects || wanted_og.count(obj.getObjGen())) {
auto j_details = JSON::makeDictionary(); auto j_details = JSON::makeDictionary();
@ -2451,8 +2446,8 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf)
// Return true as soon as we find any shared resources. // Return true as soon as we find any shared resources.
std::set<QPDFObjGen> resources_seen; // shared resources detection QPDFObjGen::set resources_seen; // shared resources detection
std::set<QPDFObjGen> nodes_seen; // loop detection QPDFObjGen::set nodes_seen; // loop detection
doIfVerbose([&](Pipeline& v, std::string const& prefix) { doIfVerbose([&](Pipeline& v, std::string const& prefix) {
v << prefix << ": " << pdf.getFilename() v << prefix << ": " << pdf.getFilename()
@ -2465,10 +2460,9 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf)
QPDFObjectHandle node = *queue.begin(); QPDFObjectHandle node = *queue.begin();
queue.pop_front(); queue.pop_front();
QPDFObjGen og = node.getObjGen(); QPDFObjGen og = node.getObjGen();
if (nodes_seen.count(og)) { if (!nodes_seen.add(og)) {
continue; continue;
} }
nodes_seen.insert(og);
QPDFObjectHandle dict = node.isStream() ? node.getDict() : node; QPDFObjectHandle dict = node.isStream() ? node.getDict() : node;
QPDFObjectHandle kids = dict.getKey("/Kids"); QPDFObjectHandle kids = dict.getKey("/Kids");
if (kids.isArray()) { if (kids.isArray()) {
@ -2489,33 +2483,29 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf)
// This is a leaf node or a form XObject. // This is a leaf node or a form XObject.
QPDFObjectHandle resources = dict.getKey("/Resources"); QPDFObjectHandle resources = dict.getKey("/Resources");
if (resources.isIndirect()) { if (resources.isIndirect()) {
QPDFObjGen resources_og = resources.getObjGen(); if (!resources_seen.add(resources)) {
if (resources_seen.count(resources_og)) {
QTC::TC("qpdf", "QPDFJob found shared resources in leaf"); QTC::TC("qpdf", "QPDFJob found shared resources in leaf");
doIfVerbose([&](Pipeline& v, std::string const& prefix) { doIfVerbose([&](Pipeline& v, std::string const& prefix) {
v << " found shared resources in leaf node " v << " found shared resources in leaf node "
<< og.unparse(' ') << ": " << og.unparse(' ') << ": "
<< resources_og.unparse(' ') << "\n"; << resources.getObjGen().unparse(' ') << "\n";
}); });
return true; return true;
} }
resources_seen.insert(resources_og);
} }
QPDFObjectHandle xobject = QPDFObjectHandle xobject =
(resources.isDictionary() ? resources.getKey("/XObject") (resources.isDictionary() ? resources.getKey("/XObject")
: QPDFObjectHandle::newNull()); : QPDFObjectHandle::newNull());
if (xobject.isIndirect()) { if (xobject.isIndirect()) {
QPDFObjGen xobject_og = xobject.getObjGen(); if (!resources_seen.add(xobject)) {
if (resources_seen.count(xobject_og)) {
QTC::TC("qpdf", "QPDFJob found shared xobject in leaf"); QTC::TC("qpdf", "QPDFJob found shared xobject in leaf");
doIfVerbose([&](Pipeline& v, std::string const& prefix) { doIfVerbose([&](Pipeline& v, std::string const& prefix) {
v << " found shared xobject in leaf node " v << " found shared xobject in leaf node "
<< og.unparse(' ') << ": " << xobject_og.unparse(' ') << og.unparse(' ') << ": "
<< "\n"; << xobject.getObjGen().unparse(' ') << "\n";
}); });
return true; return true;
} }
resources_seen.insert(xobject_og);
} }
if (xobject.isDictionary()) { if (xobject.isDictionary()) {
for (auto const& k: xobject.getKeys()) { for (auto const& k: xobject.getKeys()) {

View File

@ -1,7 +1,12 @@
#include <qpdf/QPDFObjGen.hh> #include <qpdf/QPDFObjGen.hh>
#include <qpdf/QUtil.hh> #include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QPDFObjectHelper.hh>
#include <qpdf/QPDFObject_private.hh>
#include <stdexcept>
// ABI: inline and pass og by value
std::ostream& std::ostream&
operator<<(std::ostream& os, const QPDFObjGen& og) operator<<(std::ostream& os, const QPDFObjGen& og)
{ {
@ -9,8 +14,55 @@ operator<<(std::ostream& os, const QPDFObjGen& og)
return os; return os;
} }
// ABI: inline
std::string std::string
QPDFObjGen::unparse(char separator) const QPDFObjGen::unparse(char separator) const
{ {
return std::to_string(this->obj) + separator + std::to_string(this->gen); return std::to_string(obj) + separator + std::to_string(gen);
}
bool
QPDFObjGen::set::add(QPDFObjectHandle const& oh)
{
if (auto* ptr = oh.getObjectPtr()) {
return add(ptr->getObjGen());
} else {
throw std::logic_error("attempt to retrieve QPDFObjGen from "
"uninitialized QPDFObjectHandle");
return false;
}
}
bool
QPDFObjGen::set::add(QPDFObjectHelper const& helper)
{
if (auto* ptr = helper.getObjectHandle().getObjectPtr()) {
return add(ptr->getObjGen());
} else {
throw std::logic_error("attempt to retrieve QPDFObjGen from "
"uninitialized QPDFObjectHandle");
return false;
}
}
void
QPDFObjGen::set::erase(QPDFObjectHandle const& oh)
{
if (auto* ptr = oh.getObjectPtr()) {
erase(ptr->getObjGen());
} else {
throw std::logic_error("attempt to retrieve QPDFObjGen from "
"uninitialized QPDFObjectHandle");
}
}
void
QPDFObjGen::set::erase(QPDFObjectHelper const& helper)
{
if (auto* ptr = helper.getObjectHandle().getObjectPtr()) {
erase(ptr->getObjGen());
} else {
throw std::logic_error("attempt to retrieve QPDFObjGen from "
"uninitialized QPDFObjectHandle");
}
} }

View File

@ -1588,22 +1588,12 @@ QPDFObjectHandle::rotatePage(int angle, bool relative)
int new_angle = angle; int new_angle = angle;
if (relative) { if (relative) {
int old_angle = 0; int old_angle = 0;
bool found_rotate = false;
QPDFObjectHandle cur_obj = *this; QPDFObjectHandle cur_obj = *this;
bool searched_parent = false; QPDFObjGen::set visited;
std::set<QPDFObjGen> visited; while (visited.add(cur_obj)) {
while (!found_rotate) { // Don't get stuck in an infinite loop
if (visited.count(cur_obj.getObjGen())) { if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) {
// Don't get stuck in an infinite loop
break; break;
}
if (!visited.empty()) {
searched_parent = true;
}
visited.insert(cur_obj.getObjGen());
if (cur_obj.getKey("/Rotate").isInteger()) {
found_rotate = true;
old_angle = cur_obj.getKey("/Rotate").getIntValueAsInt();
} else if (cur_obj.getKey("/Parent").isDictionary()) { } else if (cur_obj.getKey("/Parent").isDictionary()) {
cur_obj = cur_obj.getKey("/Parent"); cur_obj = cur_obj.getKey("/Parent");
} else { } else {
@ -1613,7 +1603,7 @@ QPDFObjectHandle::rotatePage(int angle, bool relative)
QTC::TC( QTC::TC(
"qpdf", "qpdf",
"QPDFObjectHandle found old angle", "QPDFObjectHandle found old angle",
searched_parent ? 0 : 1); visited.size() > 1 ? 0 : 1);
if ((old_angle % 90) != 0) { if ((old_angle % 90) != 0) {
old_angle = 0; old_angle = 0;
} }
@ -2181,20 +2171,15 @@ QPDFObjectHandle::unsafeShallowCopy()
} }
void void
QPDFObjectHandle::makeDirect( QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
std::set<QPDFObjGen>& visited, bool stop_at_streams)
{ {
assertInitialized(); assertInitialized();
auto cur_og = getObjGen(); auto cur_og = getObjGen();
if (cur_og.getObj() != 0) { if (!visited.add(cur_og)) {
if (visited.count(cur_og)) { QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop"); throw std::runtime_error("loop detected while converting object from "
throw std::runtime_error( "indirect to direct");
"loop detected while converting object from "
"indirect to direct");
}
visited.insert(cur_og);
} }
if (isBool() || isInteger() || isName() || isNull() || isReal() || if (isBool() || isInteger() || isName() || isNull() || isReal() ||
@ -2232,9 +2217,7 @@ QPDFObjectHandle::makeDirect(
"unknown object type"); "unknown object type");
} }
if (cur_og.getObj()) { visited.erase(cur_og);
visited.erase(cur_og);
}
} }
QPDFObjectHandle QPDFObjectHandle
@ -2258,7 +2241,7 @@ QPDFObjectHandle::copyStream()
void void
QPDFObjectHandle::makeDirect(bool allow_streams) QPDFObjectHandle::makeDirect(bool allow_streams)
{ {
std::set<QPDFObjGen> visited; QPDFObjGen::set visited;
makeDirect(visited, allow_streams); makeDirect(visited, allow_streams);
} }

View File

@ -15,13 +15,8 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) :
return; return;
} }
QPDFObjectHandle cur = outlines.getKey("/First"); QPDFObjectHandle cur = outlines.getKey("/First");
std::set<QPDFObjGen> seen; QPDFObjGen::set seen;
while (!cur.isNull()) { while (!cur.isNull() && seen.add(cur)) {
auto og = cur.getObjGen();
if (seen.count(og)) {
break;
}
seen.insert(og);
this->m->outlines.push_back( this->m->outlines.push_back(
QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1)); QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1));
cur = cur.getKey("/Next"); cur = cur.getKey("/Next");
@ -104,13 +99,3 @@ QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name)
} }
return result; return result;
} }
bool
QPDFOutlineDocumentHelper::checkSeen(QPDFObjGen const& og)
{
if (this->m->seen.count(og) > 0) {
return true;
}
this->m->seen.insert(og);
return false;
}

View File

@ -246,32 +246,23 @@ QPDFPageObjectHelper::getAttribute(
std::function<QPDFObjectHandle()> get_fallback, std::function<QPDFObjectHandle()> get_fallback,
bool copy_if_fallback) bool copy_if_fallback)
{ {
QPDFObjectHandle result; const bool is_form_xobject = this->oh.isFormXObject();
QPDFObjectHandle dict;
bool is_form_xobject = this->oh.isFormXObject();
bool inherited = false; bool inherited = false;
if (is_form_xobject) { auto dict = is_form_xobject ? oh.getDict() : oh;
dict = this->oh.getDict(); auto result = dict.getKey(name);
result = dict.getKey(name);
} else {
dict = this->oh;
bool inheritable =
((name == "/MediaBox") || (name == "/CropBox") ||
(name == "/Resources") || (name == "/Rotate"));
if (!is_form_xobject && result.isNull() &&
(name == "/MediaBox" || name == "/CropBox" || name == "/Resources" ||
name == "/Rotate")) {
QPDFObjectHandle node = dict; QPDFObjectHandle node = dict;
result = node.getKey(name); QPDFObjGen::set seen{};
std::set<QPDFObjGen> seen; while (seen.add(node) && node.hasKey("/Parent")) {
while (inheritable && result.isNull() && node.hasKey("/Parent")) {
seen.insert(node.getObjGen());
node = node.getKey("/Parent"); node = node.getKey("/Parent");
if (seen.count(node.getObjGen())) {
break;
}
result = node.getKey(name); result = node.getKey(name);
if (!result.isNull()) { if (!result.isNull()) {
QTC::TC("qpdf", "QPDFPageObjectHelper non-trivial inheritance"); QTC::TC("qpdf", "QPDFPageObjectHelper non-trivial inheritance");
inherited = true; inherited = true;
break;
} }
} }
} }
@ -361,30 +352,27 @@ QPDFPageObjectHelper::forEachXObject(
"QPDFPageObjectHelper::forEachXObject", "QPDFPageObjectHelper::forEachXObject",
recursive ? (this->oh.isFormXObject() ? 0 : 1) recursive ? (this->oh.isFormXObject() ? 0 : 1)
: (this->oh.isFormXObject() ? 2 : 3)); : (this->oh.isFormXObject() ? 2 : 3));
std::set<QPDFObjGen> seen; QPDFObjGen::set seen;
std::list<QPDFPageObjectHelper> queue; std::list<QPDFPageObjectHelper> queue;
queue.push_back(*this); queue.push_back(*this);
while (!queue.empty()) { while (!queue.empty()) {
QPDFPageObjectHelper ph = queue.front(); auto& ph = queue.front();
queue.pop_front(); if (seen.add(ph)) {
QPDFObjGen og = ph.oh.getObjGen(); auto xobj_dict =
if (seen.count(og)) { ph.getAttribute("/Resources", false).getKeyIfDict("/XObject");
continue; if (xobj_dict.isDictionary()) {
} for (auto const& key: xobj_dict.getKeys()) {
seen.insert(og); QPDFObjectHandle obj = xobj_dict.getKey(key);
QPDFObjectHandle resources = ph.getAttribute("/Resources", false); if ((!selector) || selector(obj)) {
if (resources.isDictionary() && resources.hasKey("/XObject")) { action(obj, xobj_dict, key);
QPDFObjectHandle xobj_dict = resources.getKey("/XObject"); }
for (auto const& key: xobj_dict.getKeys()) { if (recursive && obj.isFormXObject()) {
QPDFObjectHandle obj = xobj_dict.getKey(key); queue.emplace_back(obj);
if ((!selector) || selector(obj)) { }
action(obj, xobj_dict, key);
}
if (recursive && obj.isFormXObject()) {
queue.push_back(QPDFPageObjectHelper(obj));
} }
} }
} }
queue.pop_front();
} }
} }

View File

@ -284,7 +284,7 @@ QPDF::updateObjectMaps(
QPDFObjectHandle oh, QPDFObjectHandle oh,
std::function<int(QPDFObjectHandle&)> skip_stream_parameters) std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
{ {
std::set<QPDFObjGen> visited; QPDFObjGen::set visited;
updateObjectMapsInternal(ou, oh, skip_stream_parameters, visited, true); updateObjectMapsInternal(ou, oh, skip_stream_parameters, visited, true);
} }
@ -293,7 +293,7 @@ QPDF::updateObjectMapsInternal(
ObjUser const& ou, ObjUser const& ou,
QPDFObjectHandle oh, QPDFObjectHandle oh,
std::function<int(QPDFObjectHandle&)> skip_stream_parameters, std::function<int(QPDFObjectHandle&)> skip_stream_parameters,
std::set<QPDFObjGen>& visited, QPDFObjGen::set& visited,
bool top) bool top)
{ {
// Traverse the object tree from this point taking care to avoid // Traverse the object tree from this point taking care to avoid
@ -310,13 +310,12 @@ QPDF::updateObjectMapsInternal(
if (oh.isIndirect()) { if (oh.isIndirect()) {
QPDFObjGen og(oh.getObjGen()); QPDFObjGen og(oh.getObjGen());
if (visited.count(og)) { if (!visited.add(og)) {
QTC::TC("qpdf", "QPDF opt loop detected"); QTC::TC("qpdf", "QPDF opt loop detected");
return; return;
} }
this->m->obj_user_to_objects[ou].insert(og); this->m->obj_user_to_objects[ou].insert(og);
this->m->object_to_obj_users[og].insert(ou); this->m->object_to_obj_users[og].insert(ou);
visited.insert(og);
} }
if (oh.isArray()) { if (oh.isArray()) {

View File

@ -55,13 +55,13 @@ QPDF::getAllPages()
// initialize this->m->all_pages. // initialize this->m->all_pages.
if (this->m->all_pages.empty()) { if (this->m->all_pages.empty()) {
this->m->ever_called_get_all_pages = true; this->m->ever_called_get_all_pages = true;
std::set<QPDFObjGen> visited; QPDFObjGen::set visited;
std::set<QPDFObjGen> seen; QPDFObjGen::set seen;
QPDFObjectHandle pages = getRoot().getKey("/Pages"); QPDFObjectHandle pages = getRoot().getKey("/Pages");
bool warned = false; bool warned = false;
bool changed_pages = false; bool changed_pages = false;
while (pages.isDictionary() && pages.hasKey("/Parent")) { while (pages.isDictionary() && pages.hasKey("/Parent")) {
if (seen.count(pages.getObjGen())) { if (!seen.add(pages)) {
// loop -- will be detected again and reported later // loop -- will be detected again and reported later
break; break;
} }
@ -74,7 +74,6 @@ QPDF::getAllPages()
" to the root of the page tree; attempting to correct"); " to the root of the page tree; attempting to correct");
warned = true; warned = true;
} }
seen.insert(pages.getObjGen());
changed_pages = true; changed_pages = true;
pages = pages.getKey("/Parent"); pages = pages.getKey("/Parent");
} }
@ -92,12 +91,9 @@ QPDF::getAllPages()
void void
QPDF::getAllPagesInternal( QPDF::getAllPagesInternal(
QPDFObjectHandle cur_node, QPDFObjectHandle cur_node, QPDFObjGen::set& visited, QPDFObjGen::set& seen)
std::set<QPDFObjGen>& visited,
std::set<QPDFObjGen>& seen)
{ {
QPDFObjGen cur_node_og = cur_node.getObjGen(); if (!visited.add(cur_node)) {
if (visited.count(cur_node_og) > 0) {
throw QPDFExc( throw QPDFExc(
qpdf_e_pages, qpdf_e_pages,
this->m->file->getName(), this->m->file->getName(),
@ -105,7 +101,6 @@ QPDF::getAllPagesInternal(
0, 0,
"Loop detected in /Pages structure (getAllPages)"); "Loop detected in /Pages structure (getAllPages)");
} }
visited.insert(cur_node_og);
if (!cur_node.isDictionaryOfType("/Pages")) { if (!cur_node.isDictionaryOfType("/Pages")) {
cur_node.warnIfPossible( cur_node.warnIfPossible(
"/Type key should be /Pages but is not; overriding"); "/Type key should be /Pages but is not; overriding");
@ -125,7 +120,7 @@ QPDF::getAllPagesInternal(
" (from 0) is direct; converting to indirect"); " (from 0) is direct; converting to indirect");
kid = makeIndirectObject(kid); kid = makeIndirectObject(kid);
kids.setArrayItem(i, kid); kids.setArrayItem(i, kid);
} else if (seen.count(kid.getObjGen())) { } else if (!seen.add(kid)) {
// Make a copy of the page. This does the same as // Make a copy of the page. This does the same as
// shallowCopyPage in QPDFPageObjectHelper. // shallowCopyPage in QPDFPageObjectHelper.
QTC::TC("qpdf", "QPDF resolve duplicated page object"); QTC::TC("qpdf", "QPDF resolve duplicated page object");
@ -134,6 +129,7 @@ QPDF::getAllPagesInternal(
" (from 0) appears more than once in the pages tree;" " (from 0) appears more than once in the pages tree;"
" creating a new page object as a copy"); " creating a new page object as a copy");
kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy()); kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy());
seen.add(kid);
kids.setArrayItem(i, kid); kids.setArrayItem(i, kid);
} }
if (!kid.isDictionaryOfType("/Page")) { if (!kid.isDictionaryOfType("/Page")) {
@ -141,7 +137,6 @@ QPDF::getAllPagesInternal(
"/Type key should be /Page but is not; overriding"); "/Type key should be /Page but is not; overriding");
kid.replaceKey("/Type", "/Page"_qpdf); kid.replaceKey("/Type", "/Page"_qpdf);
} }
seen.insert(kid.getObjGen());
m->all_pages.push_back(kid); m->all_pages.push_back(kid);
} }
} }