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:
commit
a85635b839
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user