c645fd001be0719f67cd52f722a49ecdf91308ec
[swftools.git] / pdf2swf / 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 **)gmalloc(pagesSize * sizeof(Page *));
68   pageRefs = (Ref *)gmalloc(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   catDict.free();
109   return;
110
111  err3:
112   obj.free();
113  err2:
114   pagesDict.free();
115  err1:
116   catDict.free();
117   dests.initNull();
118   nameTree.initNull();
119   ok = gFalse;
120 }
121
122 Catalog::~Catalog() {
123   int i;
124
125   if (pages) {
126     for (i = 0; i < pagesSize; ++i) {
127       if (pages[i]) {
128         delete pages[i];
129       }
130     }
131     gfree(pages);
132     gfree(pageRefs);
133   }
134   dests.free();
135   nameTree.free();
136   if (baseURI) {
137     delete baseURI;
138   }
139   metadata.free();
140   structTreeRoot.free();
141   outline.free();
142 }
143
144 GString *Catalog::readMetadata() {
145   GString *s;
146   Dict *dict;
147   Object obj;
148   int c;
149
150   if (!metadata.isStream()) {
151     return NULL;
152   }
153   dict = metadata.streamGetDict();
154   if (!dict->lookup("Subtype", &obj)->isName("XML")) {
155     error(-1, "Unknown Metadata type: '%s'",
156           obj.isName() ? obj.getName() : "???");
157   }
158   obj.free();
159   s = new GString();
160   metadata.streamReset();
161   while ((c = metadata.streamGetChar()) != EOF) {
162     s->append(c);
163   }
164   metadata.streamClose();
165   return s;
166 }
167
168 int Catalog::readPageTree(Dict *pagesDict, PageAttrs *attrs, int start) {
169   Object kids;
170   Object kid;
171   Object kidRef;
172   PageAttrs *attrs1, *attrs2;
173   Page *page;
174   int i, j;
175
176   attrs1 = new PageAttrs(attrs, pagesDict);
177   pagesDict->lookup("Kids", &kids);
178   if (!kids.isArray()) {
179     error(-1, "Kids object (page %d) is wrong type (%s)",
180           start+1, kids.getTypeName());
181     goto err1;
182   }
183   for (i = 0; i < kids.arrayGetLength(); ++i) {
184     kids.arrayGet(i, &kid);
185     if (kid.isDict("Page")) {
186       attrs2 = new PageAttrs(attrs1, kid.getDict());
187       page = new Page(xref, start+1, kid.getDict(), attrs2);
188       if (!page->isOk()) {
189         ++start;
190         goto err3;
191       }
192       if (start >= pagesSize) {
193         pagesSize += 32;
194         pages = (Page **)grealloc(pages, pagesSize * sizeof(Page *));
195         pageRefs = (Ref *)grealloc(pageRefs, pagesSize * sizeof(Ref));
196         for (j = pagesSize - 32; j < pagesSize; ++j) {
197           pages[j] = NULL;
198           pageRefs[j].num = -1;
199           pageRefs[j].gen = -1;
200         }
201       }
202       pages[start] = page;
203       kids.arrayGetNF(i, &kidRef);
204       if (kidRef.isRef()) {
205         pageRefs[start].num = kidRef.getRefNum();
206         pageRefs[start].gen = kidRef.getRefGen();
207       }
208       kidRef.free();
209       ++start;
210     // This should really be isDict("Pages"), but I've seen at least one
211     // PDF file where the /Type entry is missing.
212     } else if (kid.isDict()) {
213       if ((start = readPageTree(kid.getDict(), attrs1, start))
214           < 0)
215         goto err2;
216     } else {
217       error(-1, "Kid object (page %d) is wrong type (%s)",
218             start+1, kid.getTypeName());
219       goto err2;
220     }
221     kid.free();
222   }
223   delete attrs1;
224   kids.free();
225   return start;
226
227  err3:
228   delete page;
229  err2:
230   kid.free();
231  err1:
232   kids.free();
233   delete attrs1;
234   ok = gFalse;
235   return -1;
236 }
237
238 int Catalog::findPage(int num, int gen) {
239   int i;
240
241   for (i = 0; i < numPages; ++i) {
242     if (pageRefs[i].num == num && pageRefs[i].gen == gen)
243       return i + 1;
244   }
245   return 0;
246 }
247
248 LinkDest *Catalog::findDest(GString *name) {
249   LinkDest *dest;
250   Object obj1, obj2;
251   GBool found;
252
253   // try named destination dictionary then name tree
254   found = gFalse;
255   if (dests.isDict()) {
256     if (!dests.dictLookup(name->getCString(), &obj1)->isNull())
257       found = gTrue;
258     else
259       obj1.free();
260   }
261   if (!found && nameTree.isDict()) {
262     if (!findDestInTree(&nameTree, name, &obj1)->isNull())
263       found = gTrue;
264     else
265       obj1.free();
266   }
267   if (!found)
268     return NULL;
269
270   // construct LinkDest
271   dest = NULL;
272   if (obj1.isArray()) {
273     dest = new LinkDest(obj1.getArray());
274   } else if (obj1.isDict()) {
275     if (obj1.dictLookup("D", &obj2)->isArray())
276       dest = new LinkDest(obj2.getArray());
277     else
278       error(-1, "Bad named destination value");
279     obj2.free();
280   } else {
281     error(-1, "Bad named destination value");
282   }
283   obj1.free();
284   if (dest && !dest->isOk()) {
285     delete dest;
286     dest = NULL;
287   }
288
289   return dest;
290 }
291
292 Object *Catalog::findDestInTree(Object *tree, GString *name, Object *obj) {
293   Object names, name1;
294   Object kids, kid, limits, low, high;
295   GBool done, found;
296   int cmp, i;
297
298   // leaf node
299   if (tree->dictLookup("Names", &names)->isArray()) {
300     done = found = gFalse;
301     for (i = 0; !done && i < names.arrayGetLength(); i += 2) {
302       if (names.arrayGet(i, &name1)->isString()) {
303         cmp = name->cmp(name1.getString());
304         if (cmp == 0) {
305           names.arrayGet(i+1, obj);
306           found = gTrue;
307           done = gTrue;
308         } else if (cmp < 0) {
309           done = gTrue;
310         }
311       }
312       name1.free();
313     }
314     names.free();
315     if (!found)
316       obj->initNull();
317     return obj;
318   }
319   names.free();
320
321   // root or intermediate node
322   done = gFalse;
323   if (tree->dictLookup("Kids", &kids)->isArray()) {
324     for (i = 0; !done && i < kids.arrayGetLength(); ++i) {
325       if (kids.arrayGet(i, &kid)->isDict()) {
326         if (kid.dictLookup("Limits", &limits)->isArray()) {
327           if (limits.arrayGet(0, &low)->isString() &&
328               name->cmp(low.getString()) >= 0) {
329             if (limits.arrayGet(1, &high)->isString() &&
330                 name->cmp(high.getString()) <= 0) {
331               findDestInTree(&kid, name, obj);
332               done = gTrue;
333             }
334             high.free();
335           }
336           low.free();
337         }
338         limits.free();
339       }
340       kid.free();
341     }
342   }
343   kids.free();
344
345   // name was outside of ranges of all kids
346   if (!done)
347     obj->initNull();
348
349   return obj;
350 }