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