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