Catalog.cpp
1//========================================================================
2//
3// Catalog.cpp
4//
5// Copyright 1996-2002 Glyph & Cog, LLC
6//
7//========================================================================
8
9#include <aconf.h>
10
11#ifdef USE_GCC_PRAGMAS
12#pragma implementation
13#endif
14
15#include <limits.h>
16#include <stddef.h>
17#include "gmem.h"
18#include "Object.h"
19#include "XRef.h"
20#include "Array.h"
21#include "Dict.h"
22#include "Page.h"
23#include "Error.h"
24#include "Link.h"
25#include "Catalog.h"
26
27// This define is used to limit the depth of recursive readPageTree calls
28// This is needed because the page tree nodes can reference their parents
29// leaving us in an infinite loop
30// Most sane pdf documents don't have a call depth higher than 10
31#define MAX_CALL_DEPTH 1000
32
33//------------------------------------------------------------------------
34// Catalog
35//------------------------------------------------------------------------
36
37Catalog::Catalog(XRef *xrefA) {
38 Object catDict, pagesDict;
39 Object obj, obj2;
40 int numPages0;
41 int i;
42
43 ok = gTrue;
44 xref = xrefA;
45 pages = NULL;
46 pageRefs = NULL;
47 numPages = pagesSize = 0;
48 baseURI = NULL;
49
50 xref->getCatalog(&catDict);
51 if (!catDict.isDict()) {
52 error(-1, "Catalog object is wrong type (%s)", catDict.getTypeName());
53 goto err1;
54 }
55
56 // read page tree
57 catDict.dictLookup("Pages", &pagesDict);
58 // This should really be isDict("Pages"), but I've seen at least one
59 // PDF file where the /Type entry is missing.
60 if (!pagesDict.isDict()) {
61 error(-1, "Top-level pages object is wrong type (%s)",
62 pagesDict.getTypeName());
63 goto err2;
64 }
65 pagesDict.dictLookup("Count", &obj);
66 if (!obj.isInt()) {
67 error(-1, "Page count in top-level pages object is wrong type (%s)",
68 obj.getTypeName());
69 goto err3;
70 }
71 pagesSize = numPages0 = obj.getInt();
72 obj.free();
73 if ((unsigned) pagesSize >= INT_MAX / sizeof(Page *) ||
74 (unsigned) pagesSize >= INT_MAX / sizeof(Ref)) {
75 error(-1, "Invalid 'pagesSize'");
76 ok = gFalse;
77 return;
78 }
79 pages = (Page **)gmalloc(pagesSize * sizeof(Page *));
80 pageRefs = (Ref *)gmalloc(pagesSize * sizeof(Ref));
81 for (i = 0; i < pagesSize; ++i) {
82 pages[i] = NULL;
83 pageRefs[i].num = -1;
84 pageRefs[i].gen = -1;
85 }
86 numPages = readPageTree(pagesDict.getDict(), NULL, 0, 0);
87 if (numPages != numPages0) {
88 error(-1, "Page count in top-level pages object is incorrect");
89 }
90 pagesDict.free();
91
92 // read named destination dictionary
93 catDict.dictLookup("Dests", &dests);
94
95 // read root of named destination tree
96 if (catDict.dictLookup("Names", &obj)->isDict())
97 obj.dictLookup("Dests", &nameTree);
98 else
99 nameTree.initNull();
100 obj.free();
101
102 // read base URI
103 if (catDict.dictLookup("URI", &obj)->isDict()) {
104 if (obj.dictLookup("Base", &obj2)->isString()) {
105 baseURI = obj2.getString()->copy();
106 }
107 obj2.free();
108 }
109 obj.free();
110
111 // get the metadata stream
112 catDict.dictLookup("Metadata", &metadata);
113
114 // get the structure tree root
115 catDict.dictLookup("StructTreeRoot", &structTreeRoot);
116
117 // get the outline dictionary
118 catDict.dictLookup("Outlines", &outline);
119
120 catDict.free();
121 return;
122
123 err3:
124 obj.free();
125 err2:
126 pagesDict.free();
127 err1:
128 catDict.free();
129 dests.initNull();
130 nameTree.initNull();
131 ok = gFalse;
132}
133
134Catalog::~Catalog() {
135 int i;
136
137 if (pages) {
138 for (i = 0; i < pagesSize; ++i) {
139 if (pages[i]) {
140 delete pages[i];
141 }
142 }
143 gfree(pages);
144 gfree(pageRefs);
145 }
146 dests.free();
147 nameTree.free();
148 if (baseURI) {
149 delete baseURI;
150 }
151 metadata.free();
152 structTreeRoot.free();
153 outline.free();
154}
155
156GString *Catalog::readMetadata() {
157 GString *s;
158 Dict *dict;
159 Object obj;
160 int c;
161
162 if (!metadata.isStream()) {
163 return NULL;
164 }
165 dict = metadata.streamGetDict();
166 if (!dict->lookup("Subtype", &obj)->isName("XML")) {
167 error(-1, "Unknown Metadata type: '%s'",
168 obj.isName() ? obj.getName() : "???");
169 }
170 obj.free();
171 s = new GString();
172 metadata.streamReset();
173 while ((c = metadata.streamGetChar()) != EOF) {
174 s->append(c);
175 }
176 metadata.streamClose();
177 return s;
178}
179
180int Catalog::readPageTree(Dict *pagesDict, PageAttrs *attrs, int start, int callDepth) {
181 Object kids;
182 Object kid;
183 Object kidRef;
184 PageAttrs *attrs1, *attrs2;
185 Page *page;
186 int i, j;
187
188 attrs1 = new PageAttrs(attrs, pagesDict);
189 pagesDict->lookup("Kids", &kids);
190 if (!kids.isArray()) {
191 error(-1, "Kids object (page %d) is wrong type (%s)",
192 start+1, kids.getTypeName());
193 goto err1;
194 }
195 for (i = 0; i < kids.arrayGetLength(); ++i) {
196 kids.arrayGet(i, &kid);
197 if (kid.isDict("Page")) {
198 attrs2 = new PageAttrs(attrs1, kid.getDict());
199 page = new Page(xref, start+1, kid.getDict(), attrs2);
200 if (!page->isOk()) {
201 ++start;
202 goto err3;
203 }
204 if (start >= pagesSize) {
205 pagesSize += 32;
206 if ((unsigned) pagesSize >= INT_MAX / sizeof(Page *) ||
207 (unsigned) pagesSize >= INT_MAX / sizeof(Ref)) {
208 error(-1, "Invalid 'pagesSize' parameter.");
209 goto err3;
210 }
211 pages = (Page **)grealloc(pages, pagesSize * sizeof(Page *));
212 pageRefs = (Ref *)grealloc(pageRefs, pagesSize * sizeof(Ref));
213 for (j = pagesSize - 32; j < pagesSize; ++j) {
214 pages[j] = NULL;
215 pageRefs[j].num = -1;
216 pageRefs[j].gen = -1;
217 }
218 }
219 pages[start] = page;
220 kids.arrayGetNF(i, &kidRef);
221 if (kidRef.isRef()) {
222 pageRefs[start].num = kidRef.getRefNum();
223 pageRefs[start].gen = kidRef.getRefGen();
224 }
225 kidRef.free();
226 ++start;
227 // This should really be isDict("Pages"), but I've seen at least one
228 // PDF file where the /Type entry is missing.
229 } else if (kid.isDict()) {
230 if (callDepth > MAX_CALL_DEPTH) {
231 error(-1, "Limit of %d recursive calls reached while reading the page tree. If your document is correct and not a test to try to force a crash, please report a bug.", MAX_CALL_DEPTH);
232 } else {
233 if ((start = readPageTree(kid.getDict(), attrs1, start, callDepth + 1))
234 < 0)
235 goto err2;
236 }
237 } else {
238 error(-1, "Kid object (page %d) is wrong type (%s)",
239 start+1, kid.getTypeName());
240 goto err2;
241 }
242 kid.free();
243 }
244 delete attrs1;
245 kids.free();
246 return start;
247
248 err3:
249 delete page;
250 err2:
251 kid.free();
252 err1:
253 kids.free();
254 delete attrs1;
255 ok = gFalse;
256 return -1;
257}
258
259int Catalog::findPage(int num, int gen) {
260 int i;
261
262 for (i = 0; i < numPages; ++i) {
263 if (pageRefs[i].num == num && pageRefs[i].gen == gen)
264 return i + 1;
265 }
266 return 0;
267}
268
269LinkDest *Catalog::findDest(GString *name) {
270 LinkDest *dest;
271 Object obj1, obj2;
272 GBool found;
273
274 // try named destination dictionary then name tree
275 found = gFalse;
276 if (dests.isDict()) {
277 if (!dests.dictLookup(name->getCString(), &obj1)->isNull())
278 found = gTrue;
279 else
280 obj1.free();
281 }
282 if (!found && nameTree.isDict()) {
283 if (!findDestInTree(&nameTree, name, &obj1)->isNull())
284 found = gTrue;
285 else
286 obj1.free();
287 }
288 if (!found)
289 return NULL;
290
291 // construct LinkDest
292 dest = NULL;
293 if (obj1.isArray()) {
294 dest = new LinkDest(obj1.getArray());
295 } else if (obj1.isDict()) {
296 if (obj1.dictLookup("D", &obj2)->isArray())
297 dest = new LinkDest(obj2.getArray());
298 else
299 error(-1, "Bad named destination value");
300 obj2.free();
301 } else {
302 error(-1, "Bad named destination value");
303 }
304 obj1.free();
305 if (dest && !dest->isOk()) {
306 delete dest;
307 dest = NULL;
308 }
309
310 return dest;
311}
312
313Object *Catalog::findDestInTree(Object *tree, GString *name, Object *obj) {
314 Object names, name1;
315 Object kids, kid, limits, low, high;
316 GBool done, found;
317 int cmp, i;
318
319 // leaf node
320 if (tree->dictLookup("Names", &names)->isArray()) {
321 done = found = gFalse;
322 for (i = 0; !done && i < names.arrayGetLength(); i += 2) {
323 if (names.arrayGet(i, &name1)->isString()) {
324 cmp = name->cmp(name1.getString());
325 if (cmp == 0) {
326 names.arrayGet(i+1, obj);
327 found = gTrue;
328 done = gTrue;
329 } else if (cmp < 0) {
330 done = gTrue;
331 }
332 name1.free();
333 }
334 }
335 names.free();
336 if (!found)
337 obj->initNull();
338 return obj;
339 }
340 names.free();
341
342 // root or intermediate node
343 done = gFalse;
344 if (tree->dictLookup("Kids", &kids)->isArray()) {
345 for (i = 0; !done && i < kids.arrayGetLength(); ++i) {
346 if (kids.arrayGet(i, &kid)->isDict()) {
347 if (kid.dictLookup("Limits", &limits)->isArray()) {
348 if (limits.arrayGet(0, &low)->isString() &&
349 name->cmp(low.getString()) >= 0) {
350 if (limits.arrayGet(1, &high)->isString() &&
351 name->cmp(high.getString()) <= 0) {
352 findDestInTree(&kid, name, obj);
353 done = gTrue;
354 }
355 high.free();
356 }
357 low.free();
358 }
359 limits.free();
360 }
361 kid.free();
362 }
363 }
364 kids.free();
365
366 // name was outside of ranges of all kids
367 if (!done)
368 obj->initNull();
369
370 return obj;
371}