]>
Commit | Line | Data |
---|---|---|
ef416fc2 | 1 | //======================================================================== |
2 | // | |
3 | // Catalog.cc | |
4 | // | |
5 | // Copyright 1996-2003 Glyph & Cog, LLC | |
6 | // | |
7 | //======================================================================== | |
8 | ||
9 | #include <config.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 | } |