mirror of
https://github.com/ONLYOFFICE/core.git
synced 2026-07-11 06:22:44 +08:00
1198 lines
28 KiB
C++
1198 lines
28 KiB
C++
//========================================================================
|
|
//
|
|
// Catalog.cc
|
|
//
|
|
// Copyright 1996-2013 Glyph & Cog, LLC
|
|
//
|
|
//========================================================================
|
|
|
|
#include <aconf.h>
|
|
|
|
#ifdef USE_GCC_PRAGMAS
|
|
#pragma implementation
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <limits.h>
|
|
#include "gmem.h"
|
|
#include "gmempp.h"
|
|
#include "gfile.h"
|
|
#include "GList.h"
|
|
#include "Object.h"
|
|
#include "CharTypes.h"
|
|
#include "PDFDoc.h"
|
|
#include "XRef.h"
|
|
#include "Array.h"
|
|
#include "Dict.h"
|
|
#include "Page.h"
|
|
#include "Error.h"
|
|
#include "Link.h"
|
|
#include "AcroForm.h"
|
|
#include "TextString.h"
|
|
#include "Catalog.h"
|
|
|
|
//------------------------------------------------------------------------
|
|
// PageTreeNode
|
|
//------------------------------------------------------------------------
|
|
|
|
class PageTreeNode {
|
|
public:
|
|
|
|
PageTreeNode(Ref refA, int countA, PageTreeNode *parentA);
|
|
~PageTreeNode();
|
|
|
|
Ref ref;
|
|
int count;
|
|
PageTreeNode *parent;
|
|
GList *kids; // [PageTreeNode]
|
|
PageAttrs *attrs;
|
|
};
|
|
|
|
PageTreeNode::PageTreeNode(Ref refA, int countA, PageTreeNode *parentA) {
|
|
ref = refA;
|
|
count = countA;
|
|
parent = parentA;
|
|
kids = NULL;
|
|
attrs = NULL;
|
|
}
|
|
|
|
PageTreeNode::~PageTreeNode() {
|
|
delete attrs;
|
|
if (kids) {
|
|
deleteGList(kids, PageTreeNode);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// EmbeddedFile
|
|
//------------------------------------------------------------------------
|
|
|
|
class EmbeddedFile {
|
|
public:
|
|
|
|
EmbeddedFile(TextString *nameA, Object *streamRefA);
|
|
~EmbeddedFile();
|
|
|
|
TextString *name;
|
|
Object streamRef;
|
|
};
|
|
|
|
EmbeddedFile::EmbeddedFile(TextString *nameA, Object *streamRefA) {
|
|
name = nameA;
|
|
streamRefA->copy(&streamRef);
|
|
}
|
|
|
|
EmbeddedFile::~EmbeddedFile() {
|
|
delete name;
|
|
streamRef.free();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// PageLabelNode
|
|
//------------------------------------------------------------------------
|
|
|
|
class PageLabelNode {
|
|
public:
|
|
|
|
PageLabelNode(int firstPageA, Dict *dict);
|
|
~PageLabelNode();
|
|
|
|
int firstPage; // first page number covered by this node
|
|
int lastPage; // last page number covered by this node
|
|
TextString *prefix; // label prefix (may be empty)
|
|
int start; // value of the numeric portion of this
|
|
// label for the first page in the range
|
|
char style; // page label style
|
|
};
|
|
|
|
PageLabelNode::PageLabelNode(int firstPageA, Dict *dict) {
|
|
Object prefixObj, styleObj, startObj;
|
|
|
|
// convert page index to page number
|
|
firstPage = firstPageA + 1;
|
|
|
|
// lastPage will be filled in later
|
|
lastPage = -1;
|
|
|
|
if (dict->lookup("P", &prefixObj)->isString()) {
|
|
prefix = new TextString(prefixObj.getString());
|
|
} else {
|
|
prefix = new TextString();
|
|
}
|
|
prefixObj.free();
|
|
|
|
style = '\0';
|
|
if (dict->lookup("S", &styleObj)->isName()) {
|
|
if (strlen(styleObj.getName()) == 1) {
|
|
style = styleObj.getName()[0];
|
|
}
|
|
}
|
|
styleObj.free();
|
|
|
|
start = 1;
|
|
if (dict->lookup("St", &startObj)->isInt()) {
|
|
start = startObj.getInt();
|
|
}
|
|
startObj.free();
|
|
}
|
|
|
|
PageLabelNode::~PageLabelNode() {
|
|
delete prefix;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Catalog
|
|
//------------------------------------------------------------------------
|
|
|
|
Catalog::Catalog(PDFDoc *docA) {
|
|
Object catDict;
|
|
Object obj, obj2;
|
|
|
|
ok = gTrue;
|
|
doc = docA;
|
|
xref = doc->getXRef();
|
|
pageTree = NULL;
|
|
pages = NULL;
|
|
pageRefs = NULL;
|
|
numPages = 0;
|
|
baseURI = NULL;
|
|
form = NULL;
|
|
embeddedFiles = NULL;
|
|
pageLabels = NULL;
|
|
#if MULTITHREADED
|
|
gInitMutex(&pageMutex);
|
|
#endif
|
|
|
|
xref->getCatalog(&catDict);
|
|
if (!catDict.isDict()) {
|
|
error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})",
|
|
catDict.getTypeName());
|
|
goto err1;
|
|
}
|
|
|
|
// read page tree
|
|
if (!readPageTree(&catDict)) {
|
|
goto err1;
|
|
}
|
|
|
|
// read named destination dictionary
|
|
catDict.dictLookup("Dests", &dests);
|
|
|
|
// read root of named destination tree
|
|
if (catDict.dictLookup("Names", &obj)->isDict())
|
|
obj.dictLookup("Dests", &nameTree);
|
|
else
|
|
nameTree.initNull();
|
|
obj.free();
|
|
|
|
// read base URI
|
|
if (catDict.dictLookup("URI", &obj)->isDict()) {
|
|
if (obj.dictLookup("Base", &obj2)->isString()) {
|
|
baseURI = obj2.getString()->copy();
|
|
}
|
|
obj2.free();
|
|
}
|
|
obj.free();
|
|
if (!baseURI || baseURI->getLength() == 0) {
|
|
if (baseURI) {
|
|
delete baseURI;
|
|
}
|
|
if (doc->getFileName()) {
|
|
baseURI = makePathAbsolute(grabPath(doc->getFileName()->getCString()));
|
|
if (baseURI->getChar(0) == '/') {
|
|
baseURI->insert(0, "file://localhost");
|
|
} else {
|
|
baseURI->insert(0, "file://localhost/");
|
|
}
|
|
} else {
|
|
baseURI = new GString("file://localhost/");
|
|
}
|
|
}
|
|
|
|
// get the metadata stream
|
|
catDict.dictLookup("Metadata", &metadata);
|
|
|
|
// get the structure tree root
|
|
catDict.dictLookup("StructTreeRoot", &structTreeRoot);
|
|
|
|
// get the outline dictionary
|
|
catDict.dictLookup("Outlines", &outline);
|
|
|
|
// get the AcroForm dictionary
|
|
catDict.dictLookup("AcroForm", &acroForm);
|
|
|
|
// get the NeedsRendering flag
|
|
// NB: AcroForm::load() uses this value
|
|
needsRendering = catDict.dictLookup("NeedsRendering", &obj)->isBool() &&
|
|
obj.getBool();
|
|
obj.free();
|
|
|
|
// create the Form
|
|
// (if acroForm is a null object, this will still create an AcroForm
|
|
// if there are unattached Widget-type annots)
|
|
form = AcroForm::load(doc, this, &acroForm);
|
|
|
|
// get the OCProperties dictionary
|
|
catDict.dictLookup("OCProperties", &ocProperties);
|
|
|
|
// get the list of embedded files
|
|
readEmbeddedFileList(catDict.getDict());
|
|
|
|
// get the ViewerPreferences object
|
|
catDict.dictLookupNF("ViewerPreferences", &viewerPrefs);
|
|
|
|
if (catDict.dictLookup("PageLabels", &obj)->isDict()) {
|
|
readPageLabelTree(&obj);
|
|
}
|
|
obj.free();
|
|
|
|
catDict.free();
|
|
return;
|
|
|
|
err1:
|
|
catDict.free();
|
|
dests.initNull();
|
|
nameTree.initNull();
|
|
ok = gFalse;
|
|
}
|
|
|
|
Catalog::~Catalog() {
|
|
int i;
|
|
|
|
if (pageTree) {
|
|
delete pageTree;
|
|
}
|
|
if (pages) {
|
|
for (i = 0; i < numPages; ++i) {
|
|
if (pages[i]) {
|
|
delete pages[i];
|
|
}
|
|
}
|
|
gfree(pages);
|
|
gfree(pageRefs);
|
|
}
|
|
#if MULTITHREADED
|
|
gDestroyMutex(&pageMutex);
|
|
#endif
|
|
dests.free();
|
|
nameTree.free();
|
|
if (baseURI) {
|
|
delete baseURI;
|
|
}
|
|
metadata.free();
|
|
structTreeRoot.free();
|
|
outline.free();
|
|
acroForm.free();
|
|
if (form) {
|
|
delete form;
|
|
}
|
|
ocProperties.free();
|
|
if (embeddedFiles) {
|
|
deleteGList(embeddedFiles, EmbeddedFile);
|
|
}
|
|
if (pageLabels) {
|
|
deleteGList(pageLabels, PageLabelNode);
|
|
}
|
|
viewerPrefs.free();
|
|
}
|
|
|
|
Page *Catalog::getPage(int i) {
|
|
Page *page;
|
|
|
|
#if MULTITHREADED
|
|
gLockMutex(&pageMutex);
|
|
#endif
|
|
if (!pages[i-1]) {
|
|
loadPage(i);
|
|
}
|
|
page = pages[i-1];
|
|
#if MULTITHREADED
|
|
gUnlockMutex(&pageMutex);
|
|
#endif
|
|
return page;
|
|
}
|
|
|
|
Ref *Catalog::getPageRef(int i) {
|
|
Ref *pageRef;
|
|
|
|
#if MULTITHREADED
|
|
gLockMutex(&pageMutex);
|
|
#endif
|
|
if (!pages[i-1]) {
|
|
loadPage(i);
|
|
}
|
|
pageRef = &pageRefs[i-1];
|
|
#if MULTITHREADED
|
|
gUnlockMutex(&pageMutex);
|
|
#endif
|
|
return pageRef;
|
|
}
|
|
|
|
void Catalog::doneWithPage(int i) {
|
|
#if MULTITHREADED
|
|
gLockMutex(&pageMutex);
|
|
#endif
|
|
if (pages[i-1]) {
|
|
delete pages[i-1];
|
|
pages[i-1] = NULL;
|
|
}
|
|
#if MULTITHREADED
|
|
gUnlockMutex(&pageMutex);
|
|
#endif
|
|
}
|
|
|
|
GString *Catalog::readMetadata() {
|
|
GString *s;
|
|
Dict *dict;
|
|
Object obj;
|
|
char buf[4096];
|
|
int n;
|
|
|
|
if (!metadata.isStream()) {
|
|
return NULL;
|
|
}
|
|
dict = metadata.streamGetDict();
|
|
if (!dict->lookup("Subtype", &obj)->isName("XML")) {
|
|
error(errSyntaxWarning, -1, "Unknown Metadata type: '{0:s}'",
|
|
obj.isName() ? obj.getName() : "???");
|
|
}
|
|
obj.free();
|
|
s = new GString();
|
|
metadata.streamReset();
|
|
while ((n = metadata.streamGetBlock(buf, sizeof(buf))) > 0) {
|
|
s->append(buf, n);
|
|
}
|
|
metadata.streamClose();
|
|
return s;
|
|
}
|
|
|
|
int Catalog::findPage(int num, int gen) {
|
|
int i;
|
|
|
|
#if MULTITHREADED
|
|
gLockMutex(&pageMutex);
|
|
#endif
|
|
for (i = 0; i < numPages; ++i) {
|
|
if (!pages[i]) {
|
|
loadPage(i+1);
|
|
}
|
|
if (pageRefs[i].num == num && pageRefs[i].gen == gen) {
|
|
#if MULTITHREADED
|
|
gUnlockMutex(&pageMutex);
|
|
#endif
|
|
return i + 1;
|
|
}
|
|
}
|
|
#if MULTITHREADED
|
|
gUnlockMutex(&pageMutex);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
LinkDest *Catalog::findDest(GString *name) {
|
|
LinkDest *dest;
|
|
Object obj1, obj2;
|
|
GBool found;
|
|
|
|
// try named destination dictionary then name tree
|
|
found = gFalse;
|
|
if (dests.isDict()) {
|
|
if (!dests.dictLookup(name->getCString(), &obj1)->isNull()) {
|
|
found = gTrue;
|
|
} else {
|
|
obj1.free();
|
|
}
|
|
}
|
|
if (!found && nameTree.isDict()) {
|
|
if (!findDestInTree(&nameTree, name, &obj1)->isNull()) {
|
|
found = gTrue;
|
|
} else {
|
|
obj1.free();
|
|
}
|
|
}
|
|
if (!found) {
|
|
return NULL;
|
|
}
|
|
|
|
// construct LinkDest
|
|
dest = NULL;
|
|
if (obj1.isArray()) {
|
|
dest = new LinkDest(obj1.getArray());
|
|
} else if (obj1.isDict()) {
|
|
if (obj1.dictLookup("D", &obj2)->isArray()) {
|
|
dest = new LinkDest(obj2.getArray());
|
|
} else {
|
|
error(errSyntaxWarning, -1, "Bad named destination value");
|
|
}
|
|
obj2.free();
|
|
} else {
|
|
error(errSyntaxWarning, -1, "Bad named destination value");
|
|
}
|
|
obj1.free();
|
|
if (dest && !dest->isOk()) {
|
|
delete dest;
|
|
dest = NULL;
|
|
}
|
|
|
|
return dest;
|
|
}
|
|
|
|
Object *Catalog::findDestInTree(Object *tree, GString *name, Object *obj) {
|
|
Object names, name1;
|
|
Object kids, kid, limits, low, high;
|
|
GBool done, found;
|
|
int cmp, i;
|
|
|
|
// leaf node
|
|
if (tree->dictLookup("Names", &names)->isArray()) {
|
|
done = found = gFalse;
|
|
for (i = 0; !done && i < names.arrayGetLength(); i += 2) {
|
|
if (names.arrayGet(i, &name1)->isString()) {
|
|
cmp = name->cmp(name1.getString());
|
|
if (cmp == 0) {
|
|
names.arrayGet(i+1, obj);
|
|
found = gTrue;
|
|
done = gTrue;
|
|
} else if (cmp < 0) {
|
|
done = gTrue;
|
|
}
|
|
}
|
|
name1.free();
|
|
}
|
|
names.free();
|
|
if (!found) {
|
|
obj->initNull();
|
|
}
|
|
return obj;
|
|
}
|
|
names.free();
|
|
|
|
// root or intermediate node
|
|
done = gFalse;
|
|
if (tree->dictLookup("Kids", &kids)->isArray()) {
|
|
for (i = 0; !done && i < kids.arrayGetLength(); ++i) {
|
|
if (kids.arrayGet(i, &kid)->isDict()) {
|
|
if (kid.dictLookup("Limits", &limits)->isArray()) {
|
|
if (limits.arrayGet(0, &low)->isString() &&
|
|
name->cmp(low.getString()) >= 0) {
|
|
if (limits.arrayGet(1, &high)->isString() &&
|
|
name->cmp(high.getString()) <= 0) {
|
|
findDestInTree(&kid, name, obj);
|
|
done = gTrue;
|
|
}
|
|
high.free();
|
|
}
|
|
low.free();
|
|
}
|
|
limits.free();
|
|
}
|
|
kid.free();
|
|
}
|
|
}
|
|
kids.free();
|
|
|
|
// name was outside of ranges of all kids
|
|
if (!done) {
|
|
obj->initNull();
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
GBool Catalog::readPageTree(Object *catDict) {
|
|
Object topPagesRef, topPagesObj, countObj;
|
|
int i;
|
|
|
|
if (!catDict->dictLookupNF("Pages", &topPagesRef)->isRef()) {
|
|
error(errSyntaxError, -1, "Top-level pages reference is wrong type ({0:s})",
|
|
topPagesRef.getTypeName());
|
|
topPagesRef.free();
|
|
return gFalse;
|
|
}
|
|
if (!topPagesRef.fetch(xref, &topPagesObj)->isDict()) {
|
|
error(errSyntaxError, -1, "Top-level pages object is wrong type ({0:s})",
|
|
topPagesObj.getTypeName());
|
|
topPagesObj.free();
|
|
topPagesRef.free();
|
|
return gFalse;
|
|
}
|
|
if (topPagesObj.dictLookup("Count", &countObj)->isInt()) {
|
|
numPages = countObj.getInt();
|
|
if (numPages == 0 || numPages > 50000) {
|
|
// 1. Acrobat apparently scans the page tree if it sees a zero
|
|
// count.
|
|
// 2. Absurdly large page counts result in very slow loading,
|
|
// because other code tries to fetch pages 1 through n.
|
|
// In both cases: ignore the given page count and scan the tree
|
|
// instead.
|
|
numPages = countPageTree(&topPagesObj);
|
|
}
|
|
} else {
|
|
// assume we got a Page node instead of a Pages node
|
|
numPages = 1;
|
|
}
|
|
countObj.free();
|
|
if (numPages < 0) {
|
|
error(errSyntaxError, -1, "Invalid page count");
|
|
topPagesObj.free();
|
|
topPagesRef.free();
|
|
numPages = 0;
|
|
return gFalse;
|
|
}
|
|
pageTree = new PageTreeNode(topPagesRef.getRef(), numPages, NULL);
|
|
topPagesObj.free();
|
|
topPagesRef.free();
|
|
pages = (Page **)greallocn(pages, numPages, sizeof(Page *));
|
|
pageRefs = (Ref *)greallocn(pageRefs, numPages, sizeof(Ref));
|
|
for (i = 0; i < numPages; ++i) {
|
|
pages[i] = NULL;
|
|
pageRefs[i].num = -1;
|
|
pageRefs[i].gen = -1;
|
|
}
|
|
return gTrue;
|
|
}
|
|
|
|
int Catalog::countPageTree(Object *pagesObj) {
|
|
Object kids, kid;
|
|
int n, n2, i;
|
|
|
|
if (!pagesObj->isDict()) {
|
|
return 0;
|
|
}
|
|
if (pagesObj->dictLookup("Kids", &kids)->isArray()) {
|
|
n = 0;
|
|
for (i = 0; i < kids.arrayGetLength(); ++i) {
|
|
kids.arrayGet(i, &kid);
|
|
n2 = countPageTree(&kid);
|
|
if (n2 < INT_MAX - n) {
|
|
n += n2;
|
|
} else {
|
|
error(errSyntaxError, -1, "Page tree contains too many pages");
|
|
n = INT_MAX;
|
|
}
|
|
kid.free();
|
|
}
|
|
} else {
|
|
n = 1;
|
|
}
|
|
kids.free();
|
|
return n;
|
|
}
|
|
|
|
void Catalog::loadPage(int pg) {
|
|
loadPage2(pg, pg - 1, pageTree);
|
|
}
|
|
|
|
void Catalog::loadPage2(int pg, int relPg, PageTreeNode *node) {
|
|
Object pageRefObj, pageObj, kidsObj, kidRefObj, kidObj, countObj;
|
|
PageTreeNode *kidNode, *p;
|
|
PageAttrs *attrs;
|
|
int count, i;
|
|
|
|
if (relPg >= node->count) {
|
|
error(errSyntaxError, -1, "Internal error in page tree");
|
|
pages[pg-1] = new Page(doc, pg);
|
|
return;
|
|
}
|
|
|
|
// if this node has not been filled in yet, it's either a leaf node
|
|
// or an unread internal node
|
|
if (!node->kids) {
|
|
|
|
// check for a loop in the page tree
|
|
for (p = node->parent; p; p = p->parent) {
|
|
if (node->ref.num == p->ref.num && node->ref.gen == p->ref.gen) {
|
|
error(errSyntaxError, -1, "Loop in Pages tree");
|
|
pages[pg-1] = new Page(doc, pg);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// fetch the Page/Pages object
|
|
pageRefObj.initRef(node->ref.num, node->ref.gen);
|
|
if (!pageRefObj.fetch(xref, &pageObj)->isDict()) {
|
|
error(errSyntaxError, -1, "Page tree object is wrong type ({0:s})",
|
|
pageObj.getTypeName());
|
|
pageObj.free();
|
|
pageRefObj.free();
|
|
pages[pg-1] = new Page(doc, pg);
|
|
return;
|
|
}
|
|
|
|
// merge the PageAttrs
|
|
attrs = new PageAttrs(node->parent ? node->parent->attrs
|
|
: (PageAttrs *)NULL,
|
|
pageObj.getDict());
|
|
|
|
// if "Kids" exists, it's an internal node
|
|
if (pageObj.dictLookup("Kids", &kidsObj)->isArray()) {
|
|
|
|
// save the PageAttrs
|
|
node->attrs = attrs;
|
|
|
|
// read the kids
|
|
node->kids = new GList();
|
|
for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
|
|
if (kidsObj.arrayGetNF(i, &kidRefObj)->isRef()) {
|
|
if (kidRefObj.fetch(xref, &kidObj)->isDict()) {
|
|
if (kidObj.dictLookup("Count", &countObj)->isInt()) {
|
|
count = countObj.getInt();
|
|
} else {
|
|
count = 1;
|
|
}
|
|
countObj.free();
|
|
node->kids->append(new PageTreeNode(kidRefObj.getRef(), count,
|
|
node));
|
|
} else {
|
|
error(errSyntaxError, -1, "Page tree object is wrong type ({0:s})",
|
|
kidObj.getTypeName());
|
|
}
|
|
kidObj.free();
|
|
} else {
|
|
error(errSyntaxError, -1,
|
|
"Page tree reference is wrong type ({0:s})",
|
|
kidRefObj.getTypeName());
|
|
}
|
|
kidRefObj.free();
|
|
}
|
|
|
|
} else {
|
|
|
|
// create the Page object
|
|
pageRefs[pg-1] = node->ref;
|
|
pages[pg-1] = new Page(doc, pg, pageObj.getDict(), attrs);
|
|
if (!pages[pg-1]->isOk()) {
|
|
delete pages[pg-1];
|
|
pages[pg-1] = new Page(doc, pg);
|
|
}
|
|
|
|
}
|
|
|
|
kidsObj.free();
|
|
pageObj.free();
|
|
pageRefObj.free();
|
|
}
|
|
|
|
// recursively descend the tree
|
|
if (node->kids) {
|
|
for (i = 0; i < node->kids->getLength(); ++i) {
|
|
kidNode = (PageTreeNode *)node->kids->get(i);
|
|
if (relPg < kidNode->count) {
|
|
loadPage2(pg, relPg, kidNode);
|
|
break;
|
|
}
|
|
relPg -= kidNode->count;
|
|
}
|
|
|
|
// this will only happen if the page tree is invalid
|
|
// (i.e., parent count > sum of children counts)
|
|
if (i == node->kids->getLength()) {
|
|
error(errSyntaxError, -1, "Invalid page count in page tree");
|
|
pages[pg-1] = new Page(doc, pg);
|
|
}
|
|
}
|
|
}
|
|
|
|
Object *Catalog::getDestOutputProfile(Object *destOutProf) {
|
|
Object catDict, intents, intent, subtype;
|
|
int i;
|
|
|
|
if (!xref->getCatalog(&catDict)->isDict()) {
|
|
goto err1;
|
|
}
|
|
if (!catDict.dictLookup("OutputIntents", &intents)->isArray()) {
|
|
goto err2;
|
|
}
|
|
for (i = 0; i < intents.arrayGetLength(); ++i) {
|
|
intents.arrayGet(i, &intent);
|
|
if (!intent.isDict()) {
|
|
intent.free();
|
|
continue;
|
|
}
|
|
if (!intent.dictLookup("S", &subtype)->isName("GTS_PDFX")) {
|
|
subtype.free();
|
|
intent.free();
|
|
continue;
|
|
}
|
|
subtype.free();
|
|
if (!intent.dictLookup("DestOutputProfile", destOutProf)->isStream()) {
|
|
destOutProf->free();
|
|
intent.free();
|
|
goto err2;
|
|
}
|
|
intent.free();
|
|
intents.free();
|
|
catDict.free();
|
|
return destOutProf;
|
|
}
|
|
|
|
err2:
|
|
intents.free();
|
|
err1:
|
|
catDict.free();
|
|
return NULL;
|
|
}
|
|
|
|
void Catalog::readEmbeddedFileList(Dict *catDict) {
|
|
Object obj1, obj2;
|
|
char *touchedObjs;
|
|
|
|
// read the embedded file name tree
|
|
if (catDict->lookup("Names", &obj1)->isDict()) {
|
|
if (obj1.dictLookup("EmbeddedFiles", &obj2)->isDict()) {
|
|
readEmbeddedFileTree(&obj2);
|
|
}
|
|
obj2.free();
|
|
}
|
|
obj1.free();
|
|
|
|
// look for file attachment annotations
|
|
touchedObjs = (char *)gmalloc(xref->getNumObjects());
|
|
memset(touchedObjs, 0, xref->getNumObjects());
|
|
readFileAttachmentAnnots(catDict->lookupNF("Pages", &obj1), touchedObjs);
|
|
obj1.free();
|
|
gfree(touchedObjs);
|
|
}
|
|
|
|
void Catalog::readEmbeddedFileTree(Object *node) {
|
|
Object kidsObj, kidObj;
|
|
Object namesObj, nameObj, fileSpecObj;
|
|
int i;
|
|
|
|
if (node->dictLookup("Kids", &kidsObj)->isArray()) {
|
|
for (i = 0; i < kidsObj.arrayGetLength(); ++i) {
|
|
if (kidsObj.arrayGet(i, &kidObj)->isDict()) {
|
|
readEmbeddedFileTree(&kidObj);
|
|
}
|
|
kidObj.free();
|
|
}
|
|
} else {
|
|
if (node->dictLookup("Names", &namesObj)->isArray()) {
|
|
for (i = 0; i+1 < namesObj.arrayGetLength(); ++i) {
|
|
namesObj.arrayGet(i, &nameObj);
|
|
namesObj.arrayGet(i+1, &fileSpecObj);
|
|
readEmbeddedFile(&fileSpecObj, &nameObj);
|
|
nameObj.free();
|
|
fileSpecObj.free();
|
|
}
|
|
}
|
|
namesObj.free();
|
|
}
|
|
kidsObj.free();
|
|
}
|
|
|
|
void Catalog::readFileAttachmentAnnots(Object *pageNodeRef,
|
|
char *touchedObjs) {
|
|
Object pageNode, kids, kid, annots, annot, subtype, fileSpec, contents;
|
|
int i;
|
|
|
|
// check for an invalid object reference (e.g., in a damaged PDF file)
|
|
if (pageNodeRef->getRefNum() < 0 ||
|
|
pageNodeRef->getRefNum() >= xref->getNumObjects()) {
|
|
return;
|
|
}
|
|
|
|
// check for a page tree loop
|
|
if (pageNodeRef->isRef()) {
|
|
if (touchedObjs[pageNodeRef->getRefNum()]) {
|
|
return;
|
|
}
|
|
touchedObjs[pageNodeRef->getRefNum()] = 1;
|
|
xref->fetch(pageNodeRef->getRefNum(), pageNodeRef->getRefGen(), &pageNode);
|
|
} else {
|
|
pageNodeRef->copy(&pageNode);
|
|
}
|
|
|
|
if (pageNode.isDict()) {
|
|
if (pageNode.dictLookup("Kids", &kids)->isArray()) {
|
|
for (i = 0; i < kids.arrayGetLength(); ++i) {
|
|
readFileAttachmentAnnots(kids.arrayGetNF(i, &kid), touchedObjs);
|
|
kid.free();
|
|
}
|
|
} else {
|
|
if (pageNode.dictLookup("Annots", &annots)->isArray()) {
|
|
for (i = 0; i < annots.arrayGetLength(); ++i) {
|
|
if (annots.arrayGet(i, &annot)->isDict()) {
|
|
if (annot.dictLookup("Subtype", &subtype)
|
|
->isName("FileAttachment")) {
|
|
if (annot.dictLookup("FS", &fileSpec)) {
|
|
readEmbeddedFile(&fileSpec,
|
|
annot.dictLookup("Contents", &contents));
|
|
contents.free();
|
|
}
|
|
fileSpec.free();
|
|
}
|
|
subtype.free();
|
|
}
|
|
annot.free();
|
|
}
|
|
}
|
|
annots.free();
|
|
}
|
|
kids.free();
|
|
}
|
|
|
|
pageNode.free();
|
|
}
|
|
|
|
void Catalog::readEmbeddedFile(Object *fileSpec, Object *name1) {
|
|
Object name2, efObj, streamObj;
|
|
GString *s;
|
|
TextString *name;
|
|
|
|
if (fileSpec->isDict()) {
|
|
if (fileSpec->dictLookup("UF", &name2)->isString()) {
|
|
name = new TextString(name2.getString());
|
|
} else {
|
|
name2.free();
|
|
if (fileSpec->dictLookup("F", &name2)->isString()) {
|
|
name = new TextString(name2.getString());
|
|
} else if (name1 && name1->isString()) {
|
|
name = new TextString(name1->getString());
|
|
} else {
|
|
s = new GString("?");
|
|
name = new TextString(s);
|
|
delete s;
|
|
}
|
|
}
|
|
name2.free();
|
|
if (fileSpec->dictLookup("EF", &efObj)->isDict()) {
|
|
if (efObj.dictLookupNF("F", &streamObj)->isRef()) {
|
|
if (!embeddedFiles) {
|
|
embeddedFiles = new GList();
|
|
}
|
|
embeddedFiles->append(new EmbeddedFile(name, &streamObj));
|
|
} else {
|
|
delete name;
|
|
}
|
|
streamObj.free();
|
|
} else {
|
|
delete name;
|
|
}
|
|
efObj.free();
|
|
}
|
|
}
|
|
|
|
int Catalog::getNumEmbeddedFiles() {
|
|
return embeddedFiles ? embeddedFiles->getLength() : 0;
|
|
}
|
|
|
|
Unicode *Catalog::getEmbeddedFileName(int idx) {
|
|
return ((EmbeddedFile *)embeddedFiles->get(idx))->name->getUnicode();
|
|
}
|
|
|
|
int Catalog::getEmbeddedFileNameLength(int idx) {
|
|
return ((EmbeddedFile *)embeddedFiles->get(idx))->name->getLength();
|
|
}
|
|
|
|
Object *Catalog::getEmbeddedFileStreamRef(int idx) {
|
|
return &((EmbeddedFile *)embeddedFiles->get(idx))->streamRef;
|
|
}
|
|
|
|
Object *Catalog::getEmbeddedFileStreamObj(int idx, Object *strObj) {
|
|
((EmbeddedFile *)embeddedFiles->get(idx))->streamRef.fetch(xref, strObj);
|
|
if (!strObj->isStream()) {
|
|
strObj->free();
|
|
return NULL;
|
|
}
|
|
return strObj;
|
|
}
|
|
|
|
void Catalog::readPageLabelTree(Object *root) {
|
|
PageLabelNode *label0, *label1;
|
|
int i;
|
|
|
|
pageLabels = new GList();
|
|
readPageLabelTree2(root);
|
|
|
|
if (pageLabels->getLength() == 0) {
|
|
deleteGList(pageLabels, PageLabelNode);
|
|
pageLabels = NULL;
|
|
return;
|
|
}
|
|
|
|
// set lastPage in each node
|
|
label0 = (PageLabelNode *)pageLabels->get(0);
|
|
for (i = 1; i < pageLabels->getLength(); ++i) {
|
|
label1 = (PageLabelNode *)pageLabels->get(i);
|
|
label0->lastPage = label1->firstPage - 1;
|
|
label0 = label1;
|
|
}
|
|
label0->lastPage = numPages;
|
|
}
|
|
|
|
void Catalog::readPageLabelTree2(Object *node) {
|
|
Object nums, num, labelObj, kids, kid;
|
|
int i;
|
|
|
|
if (!node->isDict()) {
|
|
return;
|
|
}
|
|
|
|
if (node->dictLookup("Nums", &nums)->isArray()) {
|
|
for (i = 0; i < nums.arrayGetLength() - 1; i += 2) {
|
|
if (nums.arrayGet(i, &num)->isInt()) {
|
|
if (nums.arrayGet(i+1, &labelObj)->isDict()) {
|
|
pageLabels->append(new PageLabelNode(num.getInt(),
|
|
labelObj.getDict()));
|
|
}
|
|
labelObj.free();
|
|
}
|
|
num.free();
|
|
}
|
|
}
|
|
nums.free();
|
|
|
|
if (node->dictLookup("Kids", &kids)->isArray()) {
|
|
for (i = 0; i < kids.arrayGetLength(); ++i) {
|
|
kids.arrayGet(i, &kid);
|
|
readPageLabelTree2(&kid);
|
|
kid.free();
|
|
}
|
|
}
|
|
kids.free();
|
|
}
|
|
|
|
TextString *Catalog::getPageLabel(int pageNum) {
|
|
PageLabelNode *label;
|
|
TextString *ts;
|
|
int pageRangeNum;
|
|
GString *suffix;
|
|
|
|
if (!pageLabels || !(label = findPageLabel(pageNum))) {
|
|
return NULL;
|
|
}
|
|
|
|
ts = new TextString(label->prefix);
|
|
|
|
pageRangeNum = label->start + (pageNum - label->firstPage);
|
|
|
|
suffix = NULL;
|
|
if (label->style == 'D') {
|
|
suffix = GString::format("{0:d}", pageRangeNum);
|
|
} else if (label->style == 'R') {
|
|
suffix = makeRomanNumeral(pageRangeNum, gTrue);
|
|
} else if (label->style == 'r') {
|
|
suffix = makeRomanNumeral(pageRangeNum, gFalse);
|
|
} else if (label->style == 'A') {
|
|
suffix = makeLetterLabel(pageRangeNum, gTrue);
|
|
} else if (label->style == 'a') {
|
|
suffix = makeLetterLabel(pageRangeNum, gFalse);
|
|
}
|
|
if (suffix) {
|
|
ts->append(suffix);
|
|
delete suffix;
|
|
}
|
|
|
|
return ts;
|
|
}
|
|
|
|
PageLabelNode *Catalog::findPageLabel(int pageNum) {
|
|
PageLabelNode *label;
|
|
int i;
|
|
|
|
//~ this could use a binary search
|
|
for (i = 0; i < pageLabels->getLength(); ++i) {
|
|
label = (PageLabelNode *)pageLabels->get(i);
|
|
if (pageNum >= label->firstPage && pageNum <= label->lastPage) {
|
|
return label;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
GString *Catalog::makeRomanNumeral(int num, GBool uppercase) {
|
|
GString *s;
|
|
|
|
s = new GString();
|
|
while (num >= 1000) {
|
|
s->append(uppercase ? 'M' : 'm');
|
|
num -= 1000;
|
|
}
|
|
if (num >= 900) {
|
|
s->append(uppercase ? "CM" : "cm");
|
|
num -= 900;
|
|
} else if (num >= 500) {
|
|
s->append(uppercase ? 'D' : 'd');
|
|
num -= 500;
|
|
} else if (num >= 400) {
|
|
s->append(uppercase ? "CD" : "cd");
|
|
num -= 400;
|
|
}
|
|
while (num >= 100) {
|
|
s->append(uppercase ? 'C' : 'c');
|
|
num -= 100;
|
|
}
|
|
if (num >= 90) {
|
|
s->append(uppercase ? "XC" : "xc");
|
|
num -= 90;
|
|
} else if (num >= 50) {
|
|
s->append(uppercase ? 'L' : 'l');
|
|
num -= 50;
|
|
} else if (num >= 40) {
|
|
s->append(uppercase ? "XL" : "xl");
|
|
num -= 40;
|
|
}
|
|
while (num >= 10) {
|
|
s->append(uppercase ? 'X' : 'x');
|
|
num -= 10;
|
|
}
|
|
if (num >= 9) {
|
|
s->append(uppercase ? "IX" : "ix");
|
|
num -= 9;
|
|
} else if (num >= 5) {
|
|
s->append(uppercase ? 'V' : 'v');
|
|
num -= 5;
|
|
} else if (num >= 4) {
|
|
s->append(uppercase ? "IV" : "iv");
|
|
num -= 4;
|
|
}
|
|
while (num >= 1) {
|
|
s->append(uppercase ? 'I' : 'i');
|
|
num -= 1;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
GString *Catalog::makeLetterLabel(int num, GBool uppercase) {
|
|
GString *s;
|
|
int m, n, i;
|
|
|
|
m = (num - 1) / 26 + 1;
|
|
n = (num - 1) % 26;
|
|
s = new GString();
|
|
for (i = 0; i < m; ++i) {
|
|
s->append((char)((uppercase ? 'A' : 'a') + n));
|
|
}
|
|
return s;
|
|
}
|
|
|
|
int Catalog::getPageNumFromPageLabel(TextString *pageLabel) {
|
|
PageLabelNode *label;
|
|
int pageNum, prefixLength, i, n;
|
|
|
|
if (!pageLabels) {
|
|
return -1;
|
|
}
|
|
for (i = 0; i < pageLabels->getLength(); ++i) {
|
|
label = (PageLabelNode *)pageLabels->get(i);
|
|
prefixLength = label->prefix->getLength();
|
|
if (pageLabel->getLength() < prefixLength ||
|
|
memcmp(pageLabel->getUnicode(), label->prefix->getUnicode(),
|
|
prefixLength * sizeof(Unicode))) {
|
|
continue;
|
|
}
|
|
if (label->style == '\0' && pageLabel->getLength() == prefixLength) {
|
|
return label->firstPage;
|
|
}
|
|
if (!convertPageLabelToInt(pageLabel, prefixLength, label->style, &n)) {
|
|
continue;
|
|
}
|
|
if (n < label->start) {
|
|
continue;
|
|
}
|
|
pageNum = label->firstPage + n - label->start;
|
|
if (pageNum <= label->lastPage) {
|
|
return pageNum;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Attempts to convert pageLabel[prefixLength .. end] to an integer,
|
|
// following the specified page label style. If successful, sets *n
|
|
// and returns true; else returns false.
|
|
GBool Catalog::convertPageLabelToInt(TextString *pageLabel, int prefixLength,
|
|
char style, int *n) {
|
|
Unicode *u;
|
|
Unicode delta;
|
|
int len, i;
|
|
|
|
len = pageLabel->getLength();
|
|
if (len <= prefixLength) {
|
|
return gFalse;
|
|
}
|
|
u = pageLabel->getUnicode();
|
|
if (style == 'D') {
|
|
*n = 0;
|
|
for (i = prefixLength; i < len; ++i) {
|
|
if (u[i] < (Unicode)'0' || u[i] > (Unicode)'9') {
|
|
return gFalse;
|
|
}
|
|
*n = *n * 10 + (u[i] - (Unicode)'0');
|
|
}
|
|
return gTrue;
|
|
} else if (style == 'R' || style == 'r') {
|
|
delta = style - 'R';
|
|
*n = 0;
|
|
i = prefixLength;
|
|
while (i < len && u[i] == (Unicode)'M' + delta) {
|
|
*n += 1000;
|
|
++i;
|
|
}
|
|
if (i+1 < len && u[i] == (Unicode)'C' + delta &&
|
|
u[i+1] == (Unicode)'M' + delta) {
|
|
*n += 900;
|
|
i += 2;
|
|
} else if (i < len && u[i] == (Unicode)'D' + delta) {
|
|
*n += 500;
|
|
++i;
|
|
} else if (i+1 < len && u[i] == (Unicode)'C' + delta &&
|
|
u[i+1] == (Unicode)'D' + delta) {
|
|
*n += 400;
|
|
i += 2;
|
|
}
|
|
while (i < len && u[i] == (Unicode)'C' + delta) {
|
|
*n += 100;
|
|
++i;
|
|
}
|
|
if (i+1 < len && u[i] == (Unicode)'X' + delta &&
|
|
u[i+1] == (Unicode)'C' + delta) {
|
|
*n += 90;
|
|
i += 2;
|
|
} else if (i < len && u[i] == (Unicode)'L' + delta) {
|
|
*n += 50;
|
|
++i;
|
|
} else if (i+1 < len && u[i] == (Unicode)'X' + delta &&
|
|
u[i+1] == (Unicode)'L' + delta) {
|
|
*n += 40;
|
|
i += 2;
|
|
}
|
|
while (i < len && u[i] == (Unicode)'X' + delta) {
|
|
*n += 10;
|
|
++i;
|
|
}
|
|
if (i+1 < len && u[i] == (Unicode)'I' + delta &&
|
|
u[i+1] == (Unicode)'X' + delta) {
|
|
*n += 9;
|
|
i += 2;
|
|
} else if (i < len && u[i] == (Unicode)'V' + delta) {
|
|
*n += 5;
|
|
++i;
|
|
} else if (i+1 < len && u[i] == (Unicode)'I' + delta &&
|
|
u[i+1] == (Unicode)'V' + delta) {
|
|
*n += 4;
|
|
i += 2;
|
|
}
|
|
while (i < len && u[i] == (Unicode)'I' + delta) {
|
|
*n += 1;
|
|
++i;
|
|
}
|
|
return i == len;
|
|
} else if (style == 'A' || style == 'a') {
|
|
if (u[prefixLength] < (Unicode)style ||
|
|
u[prefixLength] > (Unicode)style + 25) {
|
|
return gFalse;
|
|
}
|
|
for (i = prefixLength + 1; i < len; ++i) {
|
|
if (u[i] != u[prefixLength]) {
|
|
return gFalse;
|
|
}
|
|
}
|
|
*n = (len - prefixLength - 1) * 26 + (u[prefixLength] - (Unicode)style) + 1;
|
|
return gTrue;
|
|
}
|
|
return gFalse;
|
|
}
|