[poppler] poppler/Catalog.cc poppler/Catalog.h poppler/Outline.cc poppler/Outline.h poppler/PDFDoc.cc qt5/tests qt6/tests
GitLab Mirror
gitlab-mirror at kemper.freedesktop.org
Tue Jul 6 22:58:27 UTC 2021
poppler/Catalog.cc | 37 ++
poppler/Catalog.h | 2
poppler/Outline.cc | 469 +++++++++++++++++++++++++++++++++--
poppler/Outline.h | 62 +++-
poppler/PDFDoc.cc | 2
qt5/tests/CMakeLists.txt | 1
qt5/tests/check_internal_outline.cpp | 436 ++++++++++++++++++++++++++++++++
qt6/tests/CMakeLists.txt | 1
qt6/tests/check_internal_outline.cpp | 436 ++++++++++++++++++++++++++++++++
9 files changed, 1402 insertions(+), 44 deletions(-)
New commits:
commit fa494b780ab69ef04ba7447ab6d8fc3b46373e59
Author: RM <rm+git at arcsin.org>
Date: Mon May 3 12:22:16 2021 -0400
Modify internal API to allow addition and modification of outlines into a PDF. Tests in the qt5/qt6 directories.
duplicate qt5 outline test in qt6 directory
diff --git a/poppler/Catalog.cc b/poppler/Catalog.cc
index 542f449a..aa1dbf48 100644
--- a/poppler/Catalog.cc
+++ b/poppler/Catalog.cc
@@ -941,6 +941,43 @@ unsigned int Catalog::getMarkInfo()
return markInfo;
}
+Object *Catalog::getCreateOutline()
+{
+
+ catalogLocker();
+ Object catDict = xref->getCatalog();
+
+ // If there is no Object in the outline variable,
+ // check if there is an Outline dict in the catalog
+ if (outline.isNone()) {
+ if (catDict.isDict()) {
+ Object outline_obj = catDict.dictLookup("Outlines");
+ if (outline_obj.isDict()) {
+ return &outline;
+ }
+ } else {
+ // catalog is not a dict, give up?
+ return &outline;
+ }
+ }
+
+ // If there is an Object in variable, make sure it's a dict
+ if (outline.isDict()) {
+ return &outline;
+ }
+
+ // setup an empty outline dict
+ outline = Object(new Dict(doc->getXRef()));
+ outline.dictSet("Type", Object(objName, "Outlines"));
+ outline.dictSet("Count", Object(0));
+
+ const Ref outlineRef = doc->getXRef()->addIndirectObject(&outline);
+ catDict.dictAdd("Outlines", Object(outlineRef));
+ xref->setModifiedObject(&catDict, { xref->getRootNum(), xref->getRootGen() });
+
+ return &outline;
+}
+
Object *Catalog::getOutline()
{
catalogLocker();
diff --git a/poppler/Catalog.h b/poppler/Catalog.h
index 587ad1cf..254ea0d3 100644
--- a/poppler/Catalog.h
+++ b/poppler/Catalog.h
@@ -204,6 +204,8 @@ public:
bool indexToLabel(int index, GooString *label);
Object *getOutline();
+ // returns the existing outline or new one if it doesn't exist
+ Object *getCreateOutline();
Object *getAcroForm() { return &acroForm; }
void addFormToAcroForm(const Ref formRef);
diff --git a/poppler/Outline.cc b/poppler/Outline.cc
index d7814d6b..6e45b626 100644
--- a/poppler/Outline.cc
+++ b/poppler/Outline.cc
@@ -31,6 +31,7 @@
#include "goo/gmem.h"
#include "goo/GooString.h"
+#include "PDFDoc.h"
#include "XRef.h"
#include "Link.h"
#include "PDFDocEncoding.h"
@@ -39,14 +40,17 @@
//------------------------------------------------------------------------
-Outline::Outline(const Object *outlineObj, XRef *xref)
+Outline::Outline(Object *outlineObjA, XRef *xrefA, PDFDoc *docA)
{
+ outlineObj = outlineObjA;
+ xref = xrefA;
+ doc = docA;
items = nullptr;
if (!outlineObj->isDict()) {
return;
}
const Object &first = outlineObj->dictLookupNF("First");
- items = OutlineItem::readItemList(nullptr, &first, xref);
+ items = OutlineItem::readItemList(nullptr, &first, xref, doc);
}
Outline::~Outline()
@@ -59,15 +63,349 @@ Outline::~Outline()
}
}
+static void insertChildHelper(const std::string &itemTitle, int destPageNum, unsigned int pos, Ref parentObjRef, PDFDoc *doc, XRef *xref, std::vector<OutlineItem *> &items)
+{
+ std::vector<OutlineItem *>::const_iterator it;
+ if (pos >= items.size()) {
+ it = items.end();
+ } else {
+ it = items.begin() + pos;
+ }
+
+ Array *a = new Array(xref);
+ Ref *pageRef = doc->getCatalog()->getPageRef(destPageNum);
+ if (pageRef != nullptr) {
+ a->add(Object(*pageRef));
+ } else {
+ // if the page obj doesn't exist put the page number
+ // PDF32000-2008 12.3.2.2 Para 2
+ // as if it's a "Remote-Go-To Actions"
+ // it's not strictly valid, but most viewers seem
+ // to handle it without crashing
+ // alternately, could put 0, or omit it
+ a->add(Object(destPageNum - 1));
+ }
+ a->add(Object(objName, "Fit"));
+
+ Object outlineItem = Object(new Dict(xref));
+
+ GooString *g = new GooString(itemTitle);
+ outlineItem.dictSet("Title", Object(g));
+ outlineItem.dictSet("Dest", Object(a));
+ outlineItem.dictSet("Count", Object(1));
+ outlineItem.dictAdd("Parent", Object(parentObjRef));
+
+ // add one to the main outline Object's count
+ Object parentObj = xref->fetch(parentObjRef);
+ int parentCount = parentObj.dictLookup("Count").getInt();
+ parentObj.dictSet("Count", Object(parentCount + 1));
+ xref->setModifiedObject(&parentObj, parentObjRef);
+
+ Object prevItemObject;
+ Object nextItemObject;
+
+ Ref outlineItemRef = xref->addIndirectObject(&outlineItem);
+
+ // the next two statements fix up the parent object
+ // for clarity we separate this out
+ if (it == items.begin()) {
+ // we will be the first item in the list
+ // fix our parent
+ parentObj.dictSet("First", Object(outlineItemRef));
+ }
+ if (it == items.end()) {
+ // we will be the last item on the list
+ // fix up our parent
+ parentObj.dictSet("Last", Object(outlineItemRef));
+ }
+
+ if (it == items.end()) {
+ if (!items.empty()) {
+ // insert at the end, we handle this separately
+ prevItemObject = xref->fetch((*(it - 1))->getRef());
+ prevItemObject.dictSet("Next", Object(outlineItemRef));
+ outlineItem.dictSet("Prev", Object((*(it - 1))->getRef()));
+ xref->setModifiedObject(&prevItemObject, (*(it - 1))->getRef());
+ }
+ } else {
+ nextItemObject = xref->fetch((*it)->getRef());
+ nextItemObject.dictSet("Prev", Object(outlineItemRef));
+ xref->setModifiedObject(&nextItemObject, (*it)->getRef());
+
+ outlineItem.dictSet("Next", Object((*(it))->getRef()));
+
+ if (it != items.begin()) {
+ prevItemObject = xref->fetch((*(it - 1))->getRef());
+ prevItemObject.dictSet("Next", Object(outlineItemRef));
+ outlineItem.dictSet("Prev", Object((*(it - 1))->getRef()));
+ xref->setModifiedObject(&prevItemObject, (*(it - 1))->getRef());
+ }
+ }
+
+ OutlineItem *item = new OutlineItem(outlineItem.getDict(), outlineItemRef, nullptr, xref, doc);
+
+ items.insert(it, item);
+}
+
+void Outline::insertChild(const std::string &itemTitle, int destPageNum, unsigned int pos)
+{
+ Ref outlineObjRef = xref->getCatalog().dictLookupNF("Outlines").getRef();
+ insertChildHelper(itemTitle, destPageNum, pos, outlineObjRef, doc, xref, *items);
+}
+
+// ref is a valid reference to a list
+// walk the list and free any children
+// returns the number items deleted (just in case)
+static int recursiveRemoveList(Ref ref, XRef *xref)
+{
+ int count = 0;
+ bool done = false;
+
+ Ref nextRef;
+ Object tempObj;
+
+ while (!done) {
+ tempObj = xref->fetch(ref);
+
+ if (!tempObj.isDict()) {
+ // something horrible has happened
+ break;
+ }
+
+ const Object &firstRef = tempObj.dictLookupNF("First");
+ if (firstRef.isRef()) {
+ count += recursiveRemoveList(firstRef.getRef(), xref);
+ }
+
+ const Object &nextObjRef = tempObj.dictLookupNF("Next");
+ if (nextObjRef.isRef()) {
+ nextRef = nextObjRef.getRef();
+ } else {
+ done = true;
+ }
+ xref->removeIndirectObject(ref);
+ count++;
+ ref = nextRef;
+ }
+ return count;
+}
+
+static void removeChildHelper(unsigned int pos, PDFDoc *doc, XRef *xref, std::vector<OutlineItem *> &items)
+{
+ std::vector<OutlineItem *>::const_iterator it;
+ if (pos >= items.size()) {
+ // position is out of range, do nothing
+ return;
+ } else {
+ it = items.begin() + pos;
+ }
+
+ // relink around this node
+ Object itemObject = xref->fetch((*it)->getRef());
+ Object parentObj = itemObject.dictLookup("Parent");
+ Object prevItemObject = itemObject.dictLookup("Prev");
+ Object nextItemObject = itemObject.dictLookup("Next");
+
+ // delete 1 from the parent Count if it's positive
+ Object countObj = parentObj.dictLookup("Count");
+ int count = countObj.getInt();
+ if (count > 0) {
+ count--;
+ parentObj.dictSet("Count", Object(count));
+ xref->setModifiedObject(&parentObj, itemObject.dictLookupNF("Parent").getRef());
+ }
+
+ if (!prevItemObject.isNull() && !nextItemObject.isNull()) {
+ // deletion is in the middle
+ prevItemObject.dictSet("Next", Object((*(it + 1))->getRef()));
+ xref->setModifiedObject(&prevItemObject, (*(it - 1))->getRef());
+
+ nextItemObject.dictSet("Prev", Object((*(it - 1))->getRef()));
+ xref->setModifiedObject(&nextItemObject, (*(it + 1))->getRef());
+ } else if (prevItemObject.isNull() && nextItemObject.isNull()) {
+ // deletion is only child
+ parentObj.dictRemove("First");
+ parentObj.dictRemove("Last");
+ xref->setModifiedObject(&parentObj, itemObject.dictLookupNF("Parent").getRef());
+ } else if (prevItemObject.isNull()) {
+ // deletion at the front
+ parentObj.dictSet("First", Object((*(it + 1))->getRef()));
+ xref->setModifiedObject(&parentObj, itemObject.dictLookupNF("Parent").getRef());
+
+ nextItemObject.dictRemove("Prev");
+ xref->setModifiedObject(&nextItemObject, (*(it + 1))->getRef());
+ } else {
+ // deletion at the end
+ parentObj.dictSet("Last", Object((*(it - 1))->getRef()));
+ xref->setModifiedObject(&parentObj, itemObject.dictLookupNF("Parent").getRef());
+ prevItemObject.dictRemove("Next");
+ xref->setModifiedObject(&prevItemObject, (*(it - 1))->getRef());
+ }
+
+ // free any children
+ const Object &firstRef = itemObject.dictLookupNF("First");
+ if (firstRef.isRef()) {
+ recursiveRemoveList(firstRef.getRef(), xref);
+ }
+
+ // free the pdf objects and the representation
+ xref->removeIndirectObject((*it)->getRef());
+ OutlineItem *oi = *it;
+ items.erase(it);
+ // deletion of the OutlineItem will delete all child
+ // outline items in its destructor
+ delete oi;
+}
+
+void Outline::removeChild(unsigned int pos)
+{
+ removeChildHelper(pos, doc, xref, *items);
+}
+
+//------------------------------------------------------------------------
+
+int Outline::addOutlineTreeNodeList(const std::vector<OutlineTreeNode> &nodeList, Ref &parentRef, Ref &firstRef, Ref &lastRef)
+{
+ firstRef = Ref::INVALID();
+ lastRef = Ref::INVALID();
+ if (nodeList.empty()) {
+ return 0;
+ }
+
+ int itemCount = 0;
+ Ref prevNodeRef = Ref::INVALID();
+
+ for (auto &node : nodeList) {
+
+ Array *a = new Array(doc->getXRef());
+ Ref *pageRef = doc->getCatalog()->getPageRef(node.destPageNum);
+ if (pageRef != nullptr) {
+ a->add(Object(*pageRef));
+ } else {
+ // if the page obj doesn't exist put the page number
+ // PDF32000-2008 12.3.2.2 Para 2
+ // as if it's a "Remote-Go-To Actions"
+ // it's not strictly valid, but most viewers seem
+ // to handle it without crashing
+ // alternately, could put 0, or omit it
+ a->add(Object(node.destPageNum - 1));
+ }
+ a->add(Object(objName, "Fit"));
+
+ Object outlineItem = Object(new Dict(doc->getXRef()));
+ Ref outlineItemRef = doc->getXRef()->addIndirectObject(&outlineItem);
+
+ if (firstRef == Ref::INVALID()) {
+ firstRef = outlineItemRef;
+ }
+ lastRef = outlineItemRef;
+
+ GooString *g = new GooString(node.title);
+ outlineItem.dictSet("Title", Object(g));
+ outlineItem.dictSet("Dest", Object(a));
+ itemCount++;
+
+ if (prevNodeRef != Ref::INVALID()) {
+ outlineItem.dictSet("Prev", Object(prevNodeRef));
+
+ // maybe easier way to fix up the previous object
+ Object prevOutlineItem = xref->fetch(prevNodeRef);
+ prevOutlineItem.dictSet("Next", Object(outlineItemRef));
+ xref->setModifiedObject(&prevOutlineItem, prevNodeRef);
+ }
+ prevNodeRef = outlineItemRef;
+
+ Ref firstChildRef;
+ Ref lastChildRef;
+ itemCount += addOutlineTreeNodeList(node.children, outlineItemRef, firstChildRef, lastChildRef);
+
+ if (firstChildRef != Ref::INVALID()) {
+ outlineItem.dictSet("First", Object(firstChildRef));
+ outlineItem.dictSet("Last", Object(lastChildRef));
+ }
+ outlineItem.dictSet("Count", Object(itemCount));
+ outlineItem.dictAdd("Parent", Object(parentRef));
+ }
+ return itemCount;
+}
+
+/* insert an outline into a PDF
+ outline->setOutline({ {"page 1", 1,
+ { { "1.1", 1, {} } } },
+ {"page 2", 2, {} },
+ {"page 3", 3, {} },
+ {"page 4", 4,{ { "4.1", 4, {} },
+ { "4.2", 4, {} },
+ },
+ }
+ });
+ */
+
+void Outline::setOutline(const std::vector<OutlineTreeNode> &nodeList)
+{
+ // check if outlineObj is an object, if it's not make sure it exists
+ if (!outlineObj->isDict()) {
+ outlineObj = doc->getCatalog()->getCreateOutline();
+
+ // make sure it was created
+ if (!outlineObj->isDict()) {
+ return;
+ }
+ }
+
+ Ref outlineObjRef = xref->getCatalog().dictLookupNF("Outlines").getRef();
+ Ref firstChildRef;
+ Ref lastChildRef;
+
+ // free any OutlineItem objects that will be replaced
+ const Object &firstChildRefObj = outlineObj->dictLookupNF("First");
+ if (firstChildRefObj.isRef()) {
+ recursiveRemoveList(firstChildRefObj.getRef(), xref);
+ }
+
+ const int count = addOutlineTreeNodeList(nodeList, outlineObjRef, firstChildRef, lastChildRef);
+
+ // modify the parent Outlines dict
+ if (firstChildRef != Ref::INVALID()) {
+ outlineObj->dictSet("First", Object(firstChildRef));
+ outlineObj->dictSet("Last", Object(lastChildRef));
+ } else {
+ // nothing was inserted into the outline, so just remove the
+ // child references in the top-level outline
+ outlineObj->dictRemove("First");
+ outlineObj->dictRemove("Last");
+ }
+ outlineObj->dictSet("Count", Object(count));
+ xref->setModifiedObject(outlineObj, outlineObjRef);
+
+ // reload the outline object from the xrefs
+
+ if (items) {
+ for (auto entry : *items) {
+ delete entry;
+ }
+ delete items;
+ }
+ const Object &first = outlineObj->dictLookupNF("First");
+ // we probably want to allow readItemList to create an empty list
+ // but for now just check and do it ourselves here
+ if (first.isRef()) {
+ items = OutlineItem::readItemList(nullptr, &first, xref, doc);
+ } else {
+ items = new std::vector<OutlineItem *>();
+ }
+}
+
//------------------------------------------------------------------------
-OutlineItem::OutlineItem(const Dict *dict, int refNumA, OutlineItem *parentA, XRef *xrefA)
+OutlineItem::OutlineItem(const Dict *dict, Ref refA, OutlineItem *parentA, XRef *xrefA, PDFDoc *docA)
{
Object obj1;
- refNum = refNumA;
+ ref = refA;
parent = parentA;
xref = xrefA;
+ doc = docA;
title = nullptr;
kids = nullptr;
@@ -89,10 +427,6 @@ OutlineItem::OutlineItem(const Dict *dict, int refNumA, OutlineItem *parentA, XR
}
}
- firstRef = dict->lookupNF("First").copy();
- lastRef = dict->lookupNF("Last").copy();
- nextRef = dict->lookupNF("Next").copy();
-
startsOpen = false;
obj1 = dict->lookup("Count");
if (obj1.isInt()) {
@@ -109,50 +443,133 @@ OutlineItem::~OutlineItem()
delete entry;
}
delete kids;
+ kids = nullptr;
}
if (title) {
gfree(title);
}
}
-std::vector<OutlineItem *> *OutlineItem::readItemList(OutlineItem *parent, const Object *firstItemRef, XRef *xrefA)
+std::vector<OutlineItem *> *OutlineItem::readItemList(OutlineItem *parent, const Object *firstItemRef, XRef *xrefA, PDFDoc *docA)
{
auto items = new std::vector<OutlineItem *>();
- char *alreadyRead = (char *)gmalloc(xrefA->getNumObjects());
- memset(alreadyRead, 0, xrefA->getNumObjects());
+ // could be a hash (unordered_map) too for better avg case check
+ // small number of objects expected, likely doesn't matter
+ std::set<Ref> alreadyRead;
OutlineItem *parentO = parent;
while (parentO) {
- alreadyRead[parentO->refNum] = 1;
+ alreadyRead.insert(parentO->getRef());
parentO = parentO->parent;
}
- const Object *p = firstItemRef;
- while (p->isRef() && (p->getRefNum() >= 0) && (p->getRefNum() < xrefA->getNumObjects()) && !alreadyRead[p->getRefNum()]) {
- Object obj = p->fetch(xrefA);
+ Object tempObj = firstItemRef->copy();
+ while (tempObj.isRef() && (tempObj.getRefNum() >= 0) && (tempObj.getRefNum() < xrefA->getNumObjects()) && alreadyRead.find(tempObj.getRef()) == alreadyRead.end()) {
+ Object obj = tempObj.fetch(xrefA);
if (!obj.isDict()) {
break;
}
- alreadyRead[p->getRefNum()] = 1;
- OutlineItem *item = new OutlineItem(obj.getDict(), p->getRefNum(), parent, xrefA);
+ alreadyRead.insert(tempObj.getRef());
+ OutlineItem *item = new OutlineItem(obj.getDict(), tempObj.getRef(), parent, xrefA, docA);
items->push_back(item);
- p = &item->nextRef;
+ tempObj = obj.dictLookupNF("Next").copy();
+ }
+ return items;
+}
+
+void OutlineItem::open()
+{
+ if (!kids) {
+ Object itemDict = xref->fetch(ref);
+ const Object &firstRef = itemDict.dictLookupNF("First");
+ kids = readItemList(this, &firstRef, xref, doc);
}
+}
+
+void OutlineItem::setTitle(const std::string &titleA)
+{
+ gfree(title);
- gfree(alreadyRead);
+ Object dict = xref->fetch(ref);
+ GooString *g = new GooString(titleA);
+ titleLen = TextStringToUCS4(g, &title);
+ dict.dictSet("Title", Object(g));
+ xref->setModifiedObject(&dict, ref);
+}
- if (items->empty()) {
- delete items;
- items = nullptr;
+bool OutlineItem::setPageDest(int i)
+{
+ Object dict = xref->fetch(ref);
+ Object obj1;
+
+ if (i < 1) {
+ return false;
}
- return items;
+ obj1 = dict.dictLookup("Dest");
+ if (!obj1.isNull()) {
+ int arrayLength = obj1.arrayGetLength();
+ for (int index = 0; index < arrayLength; index++) {
+ obj1.arrayRemove(0);
+ }
+ obj1.arrayAdd(Object(i - 1));
+ obj1.arrayAdd(Object(objName, "Fit"));
+
+ // unique_ptr will destroy previous on assignment
+ action = LinkAction::parseDest(&obj1);
+ } else {
+ obj1 = dict.dictLookup("A");
+ if (!obj1.isNull()) {
+ // RM 20210505 Implement
+ } else {
+ }
+ return false;
+ }
+
+ xref->setModifiedObject(&dict, ref);
+ return true;
}
-void OutlineItem::open()
+void OutlineItem::insertChild(const std::string &itemTitle, int destPageNum, unsigned int pos)
{
- if (!kids) {
- kids = readItemList(this, &firstRef, xref);
+ open();
+ insertChildHelper(itemTitle, destPageNum, pos, ref, doc, xref, *kids);
+}
+
+void OutlineItem::removeChild(unsigned int pos)
+{
+ open();
+ removeChildHelper(pos, doc, xref, *kids);
+}
+
+void OutlineItem::setStartsOpen(bool value)
+{
+ startsOpen = value;
+ Object dict = xref->fetch(ref);
+ Object obj1 = dict.dictLookup("Count");
+ if (obj1.isInt()) {
+ const int count = obj1.getInt();
+ if ((count > 0 && !value) || (count < 0 && value)) {
+ // states requires change of sign
+ dict.dictSet("Count", Object(-count));
+ xref->setModifiedObject(&dict, ref);
+ }
}
}
+
+bool OutlineItem::hasKids()
+{
+ open();
+ return !kids->empty();
+}
+
+const std::vector<OutlineItem *> *OutlineItem::getKids()
+{
+ open();
+
+ if (!kids || kids->empty())
+ return nullptr;
+ else
+ return kids;
+}
diff --git a/poppler/Outline.h b/poppler/Outline.h
index 51a06fb0..8e0e7a48 100644
--- a/poppler/Outline.h
+++ b/poppler/Outline.h
@@ -30,6 +30,7 @@
#include "CharTypes.h"
#include "poppler_private_export.h"
+class PDFDoc;
class GooString;
class XRef;
class LinkAction;
@@ -37,54 +38,81 @@ class OutlineItem;
//------------------------------------------------------------------------
-class Outline
+class POPPLER_PRIVATE_EXPORT Outline
{
+ PDFDoc *doc;
+ XRef *xref;
+ Object *outlineObj; // outline dict in catalog
+
public:
- Outline(const Object *outlineObj, XRef *xref);
+ Outline(Object *outlineObj, XRef *xref, PDFDoc *doc);
~Outline();
Outline(const Outline &) = delete;
Outline &operator=(const Outline &) = delete;
- const std::vector<OutlineItem *> *getItems() const { return items; }
+ const std::vector<OutlineItem *> *getItems() const
+ {
+ if (!items || items->empty())
+ return nullptr;
+ else
+ return items;
+ }
+
+ struct OutlineTreeNode
+ {
+ std::string title;
+ int destPageNum;
+ std::vector<OutlineTreeNode> children;
+ };
+
+ // insert/remove child don't propagate changes to 'Count' up the entire
+ // tree
+ void setOutline(const std::vector<OutlineTreeNode> &nodeList);
+ void insertChild(const std::string &itemTitle, int destPageNum, unsigned int pos);
+ void removeChild(unsigned int pos);
private:
- std::vector<OutlineItem *> *items; // nullptr if document has no outline,
+ std::vector<OutlineItem *> *items; // nullptr if document has no outline
+ int addOutlineTreeNodeList(const std::vector<OutlineTreeNode> &nodeList, Ref &parentRef, Ref &firstRef, Ref &lastRef);
};
//------------------------------------------------------------------------
class POPPLER_PRIVATE_EXPORT OutlineItem
{
+ friend Outline;
+
public:
- OutlineItem(const Dict *dict, int refNumA, OutlineItem *parentA, XRef *xrefA);
+ OutlineItem(const Dict *dict, Ref refA, OutlineItem *parentA, XRef *xrefA, PDFDoc *docA);
~OutlineItem();
-
OutlineItem(const OutlineItem &) = delete;
OutlineItem &operator=(const OutlineItem &) = delete;
-
- static std::vector<OutlineItem *> *readItemList(OutlineItem *parent, const Object *firstItemRef, XRef *xrefA);
-
- void open();
-
+ static std::vector<OutlineItem *> *readItemList(OutlineItem *parent, const Object *firstItemRef, XRef *xrefA, PDFDoc *docA);
const Unicode *getTitle() const { return title; }
+ void setTitle(const std::string &titleA);
int getTitleLength() const { return titleLen; }
+ bool setPageDest(int i);
// OutlineItem keeps the ownership of the action
const LinkAction *getAction() const { return action.get(); }
+ void setStartsOpen(bool value);
bool isOpen() const { return startsOpen; }
- bool hasKids() const { return firstRef.isRef(); }
- const std::vector<OutlineItem *> *getKids() const { return kids; }
+ bool hasKids();
+ void open();
+ const std::vector<OutlineItem *> *getKids();
+ int getRefNum() const { return ref.num; }
+ Ref getRef() const { return ref; }
+ void insertChild(const std::string &itemTitle, int destPageNum, unsigned int pos);
+ void removeChild(unsigned int pos);
private:
- int refNum;
+ Ref ref;
OutlineItem *parent;
+ PDFDoc *doc;
XRef *xref;
Unicode *title;
int titleLen;
std::unique_ptr<LinkAction> action;
- Object firstRef;
- Object lastRef;
- Object nextRef;
bool startsOpen;
std::vector<OutlineItem *> *kids; // nullptr if this item is closed or has no kids
};
diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index 90de9b8b..941f7a51 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -1933,7 +1933,7 @@ Outline *PDFDoc::getOutline()
if (!outline) {
pdfdocLocker();
// read outline
- outline = new Outline(catalog->getOutline(), xref);
+ outline = new Outline(catalog->getOutline(), xref, this);
}
return outline;
diff --git a/qt5/tests/CMakeLists.txt b/qt5/tests/CMakeLists.txt
index c3decb92..8293a3a1 100644
--- a/qt5/tests/CMakeLists.txt
+++ b/qt5/tests/CMakeLists.txt
@@ -70,6 +70,7 @@ qt5_add_qtest(check_qt5_permissions check_permissions.cpp)
qt5_add_qtest(check_qt5_search check_search.cpp)
qt5_add_qtest(check_qt5_actualtext check_actualtext.cpp)
qt5_add_qtest(check_qt5_lexer check_lexer.cpp)
+qt5_add_qtest(check_qt5_internal_outline check_internal_outline.cpp)
qt5_add_qtest(check_qt5_goostring check_goostring.cpp)
qt5_add_qtest(check_qt5_object check_object.cpp)
qt5_add_qtest(check_qt5_stroke_opacity check_stroke_opacity.cpp)
diff --git a/qt5/tests/check_internal_outline.cpp b/qt5/tests/check_internal_outline.cpp
new file mode 100644
index 00000000..0119d909
--- /dev/null
+++ b/qt5/tests/check_internal_outline.cpp
@@ -0,0 +1,436 @@
+#include <QtTest/QtTest>
+
+#include "Outline.h"
+#include "PDFDoc.h"
+#include "PDFDocFactory.h"
+
+class TestInternalOutline : public QObject
+{
+ Q_OBJECT
+public:
+ TestInternalOutline(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+ void testCreateOutline();
+ void testSetOutline();
+ void testInsertChild();
+ void testRemoveChild();
+ void testSetTitleAndSetPageDest();
+};
+
+void TestInternalOutline::testCreateOutline()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an empty outline and save the file
+ outline->setOutline({});
+ outlineItems = outline->getItems();
+ // no items will result in a nullptr rather than a 0 length list
+ QVERIFY(outlineItems == nullptr);
+ doc->saveAs(&gooTempFileName);
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline with no items
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+}
+
+static std::string getTitle(const OutlineItem *item)
+{
+ const Unicode *u = item->getTitle();
+ std::string s;
+ for (int i = 0; i < item->getTitleLength(); i++) {
+ s.append(1, (char)u[i]);
+ }
+ return s;
+}
+
+void TestInternalOutline::testSetOutline()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an outline and save the file
+ outline->setOutline(
+ { { "1", 1, { { "1.1", 1, {} }, { "1.2", 2, {} }, { "1.3", 3, { { "1.3.1", 1, {} }, { "1.3.2", 2, {} }, { "1.3.3", 3, {} }, { "1.3.4", 4, {} } } }, { "1.4", 4, {} } } }, { "2", 2, {} }, { "3", 3, {} }, { "4", 4, {} } });
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems != nullptr);
+ doc->saveAs(&gooTempFileName);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ outlineItems = outline->getItems();
+
+ QVERIFY(outlineItems != nullptr);
+ QVERIFY(outlineItems->size() == 4);
+
+ OutlineItem *item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+
+ // c_str() is used so QCOMPARE prints string correctly on disagree
+ QCOMPARE(getTitle(item).c_str(), "1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "3");
+ item = outlineItems->at(3);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "4");
+
+ outlineItems = outlineItems->at(0)->getKids();
+ QVERIFY(outlineItems != nullptr);
+ item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3");
+ item = outlineItems->at(3);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.4");
+
+ outlineItems = outlineItems->at(2)->getKids();
+ QVERIFY(outlineItems != nullptr);
+
+ item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.3");
+ item = outlineItems->at(3);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.4");
+}
+
+void TestInternalOutline::testInsertChild()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+ QTemporaryFile tempFile2;
+ QVERIFY(tempFile2.open());
+ tempFile2.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+ const std::string tempFileName2 = tempFile2.fileName().toStdString();
+ const GooString gooTempFileName2 { tempFileName2 };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an outline and save the file
+ outline->setOutline({});
+ doc->saveAs(&gooTempFileName);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline with no items
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ // nullptr for 0-length
+ QVERIFY(outline->getItems() == nullptr);
+
+ // insert first one to empty
+ outline->insertChild("2", 1, 0);
+ // insert at the end
+ outline->insertChild("3", 1, 1);
+ // insert at the start
+ outline->insertChild("1", 1, 0);
+
+ // add an item to "2"
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems != nullptr);
+ QVERIFY(outlineItems->at(1));
+ outlineItems->at(1)->insertChild("2.1", 2, 0);
+ outlineItems->at(1)->insertChild("2.2", 2, 1);
+ outlineItems->at(1)->insertChild("2.4", 2, 2);
+
+ outlineItems->at(1)->insertChild("2.3", 2, 2);
+
+ // save the file
+ doc->saveAs(&gooTempFileName2);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName2);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ outlineItems = outline->getItems();
+
+ QVERIFY(outlineItems != nullptr);
+ QVERIFY(outlineItems->size() == 3);
+
+ OutlineItem *item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+
+ // c_str() is used so QCOMPARE prints string correctly on disagree
+ QCOMPARE(getTitle(item).c_str(), "1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "3");
+
+ outlineItems = outlineItems->at(1)->getKids();
+ item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2.1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2.2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2.3");
+ item = outlineItems->at(3);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2.4");
+}
+
+void TestInternalOutline::testRemoveChild()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+
+ QTemporaryFile tempFile2;
+ QVERIFY(tempFile2.open());
+ tempFile2.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+ const std::string tempFileName2 = tempFile2.fileName().toStdString();
+ const GooString gooTempFileName2 { tempFileName2 };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an outline and save the file
+ outline->setOutline({ { "1", 1, { { "1.1", 1, {} }, { "1.2", 2, {} }, { "1.3", 3, { { "1.3.1", 1, {} }, { "1.3.2", 2, {} }, { "1.3.3", 3, {} }, { "1.3.4", 4, {} } } }, { "1.4", 4, {} } } },
+ { "2", 2, { { "2.1", 1, {} } } },
+ { "3", 3, { { "3.1", 1, {} }, { "3.2", 2, { { "3.2.1", 1, {} } } } } },
+ { "4", 4, {} } });
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems != nullptr);
+ doc->saveAs(&gooTempFileName);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ // remove "3"
+ outline->removeChild(2);
+ // remove "1.3.1"
+ outline->getItems()->at(0)->getKids()->at(2)->removeChild(0);
+ // remove "1.3.4"
+ outline->getItems()->at(0)->getKids()->at(2)->removeChild(2);
+ // remove "2.1"
+ outline->getItems()->at(1)->removeChild(0);
+
+ // save the file
+ doc->saveAs(&gooTempFileName2);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName2);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ outlineItems = outline->getItems();
+
+ QVERIFY(outlineItems != nullptr);
+ QVERIFY(outlineItems->size() == 3);
+
+ OutlineItem *item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+
+ // c_str() is used so QCOMPARE prints string correctly on disagree
+ QCOMPARE(getTitle(item).c_str(), "1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "4");
+
+ outlineItems = outlineItems->at(0)->getKids();
+ outlineItems = outlineItems->at(2)->getKids();
+ item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.2");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.3");
+
+ // verify "2.1" is removed, lst length 0 is returned as a nullptr
+ QVERIFY(outline->getItems()->at(1)->getKids() == nullptr);
+}
+
+void TestInternalOutline::testSetTitleAndSetPageDest()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+
+ QTemporaryFile tempFile2;
+ QVERIFY(tempFile2.open());
+ tempFile2.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+ const std::string tempFileName2 = tempFile2.fileName().toStdString();
+ const GooString gooTempFileName2 { tempFileName2 };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an outline and save the file
+ outline->setOutline({ { "1", 1, { { "1.1", 1, {} }, { "1.2", 2, {} }, { "1.3", 3, { { "1.3.1", 1, {} }, { "1.3.2", 2, {} }, { "1.3.3", 3, {} }, { "1.3.4", 4, {} } } }, { "1.4", 4, {} } } },
+ { "2", 2, { { "2.1", 1, {} } } },
+ { "3", 3, { { "3.1", 1, {} }, { "3.2", 2, { { "3.2.1", 1, {} } } } } },
+ { "4", 4, {} } });
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems != nullptr);
+ doc->saveAs(&gooTempFileName);
+
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ // change "1.3.1"
+ OutlineItem *item = outline->getItems()->at(0)->getKids()->at(2)->getKids()->at(0);
+ QCOMPARE(getTitle(item).c_str(), "1.3.1");
+
+ item->setTitle("Changed to a different title");
+
+ item = outline->getItems()->at(2);
+ {
+ const LinkAction *action = item->getAction();
+ QVERIFY(action->getKind() == actionGoTo);
+ const LinkGoTo *gotoAction = dynamic_cast<const LinkGoTo *>(action);
+ const LinkDest *dest = gotoAction->getDest();
+ QVERIFY(dest->isPageRef() == false);
+ QCOMPARE(dest->getPageNum(), 3);
+
+ item->setPageDest(1);
+ }
+
+ // save the file
+ doc->saveAs(&gooTempFileName2);
+ outline = nullptr;
+ item = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName2);
+ QVERIFY(doc.get());
+
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ item = outline->getItems()->at(0)->getKids()->at(2)->getKids()->at(0);
+ QCOMPARE(getTitle(item).c_str(), "Changed to a different title");
+ {
+ item = outline->getItems()->at(2);
+ const LinkAction *action = item->getAction();
+ QVERIFY(action->getKind() == actionGoTo);
+ const LinkGoTo *gotoAction = dynamic_cast<const LinkGoTo *>(action);
+ const LinkDest *dest = gotoAction->getDest();
+ QVERIFY(dest->isPageRef() == false);
+ QCOMPARE(dest->getPageNum(), 1);
+ }
+}
+
+QTEST_GUILESS_MAIN(TestInternalOutline)
+#include "check_internal_outline.moc"
diff --git a/qt6/tests/CMakeLists.txt b/qt6/tests/CMakeLists.txt
index 72cc4860..d72614f3 100644
--- a/qt6/tests/CMakeLists.txt
+++ b/qt6/tests/CMakeLists.txt
@@ -60,6 +60,7 @@ qt6_add_qtest(check_qt6_permissions check_permissions.cpp)
qt6_add_qtest(check_qt6_search check_search.cpp)
qt6_add_qtest(check_qt6_actualtext check_actualtext.cpp)
qt6_add_qtest(check_qt6_lexer check_lexer.cpp)
+qt6_add_qtest(check_qt6_internal_outline check_internal_outline.cpp)
qt6_add_qtest(check_qt6_goostring check_goostring.cpp)
qt6_add_qtest(check_qt6_object check_object.cpp)
qt6_add_qtest(check_qt6_stroke_opacity check_stroke_opacity.cpp)
diff --git a/qt6/tests/check_internal_outline.cpp b/qt6/tests/check_internal_outline.cpp
new file mode 100644
index 00000000..0119d909
--- /dev/null
+++ b/qt6/tests/check_internal_outline.cpp
@@ -0,0 +1,436 @@
+#include <QtTest/QtTest>
+
+#include "Outline.h"
+#include "PDFDoc.h"
+#include "PDFDocFactory.h"
+
+class TestInternalOutline : public QObject
+{
+ Q_OBJECT
+public:
+ TestInternalOutline(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+ void testCreateOutline();
+ void testSetOutline();
+ void testInsertChild();
+ void testRemoveChild();
+ void testSetTitleAndSetPageDest();
+};
+
+void TestInternalOutline::testCreateOutline()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an empty outline and save the file
+ outline->setOutline({});
+ outlineItems = outline->getItems();
+ // no items will result in a nullptr rather than a 0 length list
+ QVERIFY(outlineItems == nullptr);
+ doc->saveAs(&gooTempFileName);
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline with no items
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+}
+
+static std::string getTitle(const OutlineItem *item)
+{
+ const Unicode *u = item->getTitle();
+ std::string s;
+ for (int i = 0; i < item->getTitleLength(); i++) {
+ s.append(1, (char)u[i]);
+ }
+ return s;
+}
+
+void TestInternalOutline::testSetOutline()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an outline and save the file
+ outline->setOutline(
+ { { "1", 1, { { "1.1", 1, {} }, { "1.2", 2, {} }, { "1.3", 3, { { "1.3.1", 1, {} }, { "1.3.2", 2, {} }, { "1.3.3", 3, {} }, { "1.3.4", 4, {} } } }, { "1.4", 4, {} } } }, { "2", 2, {} }, { "3", 3, {} }, { "4", 4, {} } });
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems != nullptr);
+ doc->saveAs(&gooTempFileName);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ outlineItems = outline->getItems();
+
+ QVERIFY(outlineItems != nullptr);
+ QVERIFY(outlineItems->size() == 4);
+
+ OutlineItem *item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+
+ // c_str() is used so QCOMPARE prints string correctly on disagree
+ QCOMPARE(getTitle(item).c_str(), "1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "3");
+ item = outlineItems->at(3);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "4");
+
+ outlineItems = outlineItems->at(0)->getKids();
+ QVERIFY(outlineItems != nullptr);
+ item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3");
+ item = outlineItems->at(3);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.4");
+
+ outlineItems = outlineItems->at(2)->getKids();
+ QVERIFY(outlineItems != nullptr);
+
+ item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.3");
+ item = outlineItems->at(3);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.4");
+}
+
+void TestInternalOutline::testInsertChild()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+ QTemporaryFile tempFile2;
+ QVERIFY(tempFile2.open());
+ tempFile2.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+ const std::string tempFileName2 = tempFile2.fileName().toStdString();
+ const GooString gooTempFileName2 { tempFileName2 };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an outline and save the file
+ outline->setOutline({});
+ doc->saveAs(&gooTempFileName);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline with no items
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ // nullptr for 0-length
+ QVERIFY(outline->getItems() == nullptr);
+
+ // insert first one to empty
+ outline->insertChild("2", 1, 0);
+ // insert at the end
+ outline->insertChild("3", 1, 1);
+ // insert at the start
+ outline->insertChild("1", 1, 0);
+
+ // add an item to "2"
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems != nullptr);
+ QVERIFY(outlineItems->at(1));
+ outlineItems->at(1)->insertChild("2.1", 2, 0);
+ outlineItems->at(1)->insertChild("2.2", 2, 1);
+ outlineItems->at(1)->insertChild("2.4", 2, 2);
+
+ outlineItems->at(1)->insertChild("2.3", 2, 2);
+
+ // save the file
+ doc->saveAs(&gooTempFileName2);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName2);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ outlineItems = outline->getItems();
+
+ QVERIFY(outlineItems != nullptr);
+ QVERIFY(outlineItems->size() == 3);
+
+ OutlineItem *item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+
+ // c_str() is used so QCOMPARE prints string correctly on disagree
+ QCOMPARE(getTitle(item).c_str(), "1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "3");
+
+ outlineItems = outlineItems->at(1)->getKids();
+ item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2.1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2.2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2.3");
+ item = outlineItems->at(3);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2.4");
+}
+
+void TestInternalOutline::testRemoveChild()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+
+ QTemporaryFile tempFile2;
+ QVERIFY(tempFile2.open());
+ tempFile2.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+ const std::string tempFileName2 = tempFile2.fileName().toStdString();
+ const GooString gooTempFileName2 { tempFileName2 };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an outline and save the file
+ outline->setOutline({ { "1", 1, { { "1.1", 1, {} }, { "1.2", 2, {} }, { "1.3", 3, { { "1.3.1", 1, {} }, { "1.3.2", 2, {} }, { "1.3.3", 3, {} }, { "1.3.4", 4, {} } } }, { "1.4", 4, {} } } },
+ { "2", 2, { { "2.1", 1, {} } } },
+ { "3", 3, { { "3.1", 1, {} }, { "3.2", 2, { { "3.2.1", 1, {} } } } } },
+ { "4", 4, {} } });
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems != nullptr);
+ doc->saveAs(&gooTempFileName);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ // remove "3"
+ outline->removeChild(2);
+ // remove "1.3.1"
+ outline->getItems()->at(0)->getKids()->at(2)->removeChild(0);
+ // remove "1.3.4"
+ outline->getItems()->at(0)->getKids()->at(2)->removeChild(2);
+ // remove "2.1"
+ outline->getItems()->at(1)->removeChild(0);
+
+ // save the file
+ doc->saveAs(&gooTempFileName2);
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName2);
+ QVERIFY(doc.get());
+
+ // ensure the re-opened file has an outline
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ outlineItems = outline->getItems();
+
+ QVERIFY(outlineItems != nullptr);
+ QVERIFY(outlineItems->size() == 3);
+
+ OutlineItem *item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+
+ // c_str() is used so QCOMPARE prints string correctly on disagree
+ QCOMPARE(getTitle(item).c_str(), "1");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "2");
+ item = outlineItems->at(2);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "4");
+
+ outlineItems = outlineItems->at(0)->getKids();
+ outlineItems = outlineItems->at(2)->getKids();
+ item = outlineItems->at(0);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.2");
+ item = outlineItems->at(1);
+ QVERIFY(item != nullptr);
+ QCOMPARE(getTitle(item).c_str(), "1.3.3");
+
+ // verify "2.1" is removed, lst length 0 is returned as a nullptr
+ QVERIFY(outline->getItems()->at(1)->getKids() == nullptr);
+}
+
+void TestInternalOutline::testSetTitleAndSetPageDest()
+{
+ QTemporaryFile tempFile;
+ QVERIFY(tempFile.open());
+ tempFile.close();
+
+ QTemporaryFile tempFile2;
+ QVERIFY(tempFile2.open());
+ tempFile2.close();
+
+ const std::string tempFileName = tempFile.fileName().toStdString();
+ const GooString gooTempFileName { tempFileName };
+ const std::string tempFileName2 = tempFile2.fileName().toStdString();
+ const GooString gooTempFileName2 { tempFileName2 };
+
+ std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
+ QVERIFY(doc.get());
+
+ // ensure the file has no existing outline
+ Outline *outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+ auto *outlineItems = outline->getItems();
+ QVERIFY(outlineItems == nullptr);
+
+ // create an outline and save the file
+ outline->setOutline({ { "1", 1, { { "1.1", 1, {} }, { "1.2", 2, {} }, { "1.3", 3, { { "1.3.1", 1, {} }, { "1.3.2", 2, {} }, { "1.3.3", 3, {} }, { "1.3.4", 4, {} } } }, { "1.4", 4, {} } } },
+ { "2", 2, { { "2.1", 1, {} } } },
+ { "3", 3, { { "3.1", 1, {} }, { "3.2", 2, { { "3.2.1", 1, {} } } } } },
+ { "4", 4, {} } });
+ outlineItems = outline->getItems();
+ QVERIFY(outlineItems != nullptr);
+ doc->saveAs(&gooTempFileName);
+
+ outline = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName);
+ QVERIFY(doc.get());
+
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ // change "1.3.1"
+ OutlineItem *item = outline->getItems()->at(0)->getKids()->at(2)->getKids()->at(0);
+ QCOMPARE(getTitle(item).c_str(), "1.3.1");
+
+ item->setTitle("Changed to a different title");
+
+ item = outline->getItems()->at(2);
+ {
+ const LinkAction *action = item->getAction();
+ QVERIFY(action->getKind() == actionGoTo);
+ const LinkGoTo *gotoAction = dynamic_cast<const LinkGoTo *>(action);
+ const LinkDest *dest = gotoAction->getDest();
+ QVERIFY(dest->isPageRef() == false);
+ QCOMPARE(dest->getPageNum(), 3);
+
+ item->setPageDest(1);
+ }
+
+ // save the file
+ doc->saveAs(&gooTempFileName2);
+ outline = nullptr;
+ item = nullptr;
+
+ /******************************************************/
+
+ doc = PDFDocFactory().createPDFDoc(gooTempFileName2);
+ QVERIFY(doc.get());
+
+ outline = doc->getOutline();
+ QVERIFY(outline != nullptr);
+
+ item = outline->getItems()->at(0)->getKids()->at(2)->getKids()->at(0);
+ QCOMPARE(getTitle(item).c_str(), "Changed to a different title");
+ {
+ item = outline->getItems()->at(2);
+ const LinkAction *action = item->getAction();
+ QVERIFY(action->getKind() == actionGoTo);
+ const LinkGoTo *gotoAction = dynamic_cast<const LinkGoTo *>(action);
+ const LinkDest *dest = gotoAction->getDest();
+ QVERIFY(dest->isPageRef() == false);
+ QCOMPARE(dest->getPageNum(), 1);
+ }
+}
+
+QTEST_GUILESS_MAIN(TestInternalOutline)
+#include "check_internal_outline.moc"
More information about the poppler
mailing list