w32tex
About: TeX Live provides a comprehensive TeX system including all the major TeX-related programs, macro packages, and fonts that are free software. Windows sources.
  Fossies Dox: w32tex-src.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

Catalog.cc
Go to the documentation of this file.
1 //========================================================================
2 //
3 // Catalog.cc
4 //
5 // Copyright 1996-2007 Glyph & Cog, LLC
6 //
7 //========================================================================
8 
9 //========================================================================
10 //
11 // Modified under the Poppler project - http://poppler.freedesktop.org
12 //
13 // All changes made under the Poppler project to this file are licensed
14 // under GPL version 2 or later
15 //
16 // Copyright (C) 2005 Kristian Høgsberg <krh@redhat.com>
17 // Copyright (C) 2005-2013, 2015, 2017-2020 Albert Astals Cid <aacid@kde.org>
18 // Copyright (C) 2005 Jeff Muizelaar <jrmuizel@nit.ca>
19 // Copyright (C) 2005 Jonathan Blandford <jrb@redhat.com>
20 // Copyright (C) 2005 Marco Pesenti Gritti <mpg@redhat.com>
21 // Copyright (C) 2005, 2006, 2008 Brad Hards <bradh@frogmouth.net>
22 // Copyright (C) 2006, 2008, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
23 // Copyright (C) 2007 Julien Rebetez <julienr@svn.gnome.org>
24 // Copyright (C) 2008, 2011 Pino Toscano <pino@kde.org>
25 // Copyright (C) 2009 Ilya Gorenbein <igorenbein@finjan.com>
26 // Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
27 // Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
28 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
29 // Copyright (C) 2013 Julien Nabet <serval2412@yahoo.fr>
30 // Copyright (C) 2013 Adrian Perez de Castro <aperez@igalia.com>
31 // Copyright (C) 2013, 2017 Adrian Johnson <ajohnson@redneon.com>
32 // Copyright (C) 2013 José Aliste <jaliste@src.gnome.org>
33 // Copyright (C) 2014 Ed Porras <ed@moto-research.com>
34 // Copyright (C) 2015 Even Rouault <even.rouault@spatialys.com>
35 // Copyright (C) 2016 Masamichi Hosoda <trueroad@trueroad.jp>
36 // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
37 // Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
38 // Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de>
39 // Copyright (C) 2020 Katarina Behrens <Katarina.Behrens@cib.de>
40 // Copyright (C) 2020 Thorsten Behrens <Thorsten.Behrens@CIB.de>
41 // Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by Technische Universität Dresden
42 //
43 // To see a description of the changes please see the Changelog file that
44 // came with your tarball or type make ChangeLog if you are building from git
45 //
46 //========================================================================
47 
48 #include <config.h>
49 
50 #include <cstddef>
51 #include <cstdlib>
52 #include "goo/gmem.h"
53 #include "Object.h"
54 #include "PDFDoc.h"
55 #include "XRef.h"
56 #include "Array.h"
57 #include "Dict.h"
58 #include "Page.h"
59 #include "Error.h"
60 #include "Link.h"
61 #include "PageLabelInfo.h"
62 #include "Catalog.h"
63 #include "Form.h"
64 #include "OptionalContent.h"
65 #include "ViewerPreferences.h"
66 #include "FileSpec.h"
67 #include "StructTreeRoot.h"
68 
69 //------------------------------------------------------------------------
70 // Catalog
71 //------------------------------------------------------------------------
72 
73 #define catalogLocker() std::unique_lock<std::recursive_mutex> locker(mutex)
74 
76 {
77  ok = true;
78  doc = docA;
79  xref = doc->getXRef();
80  numPages = -1;
81  baseURI = nullptr;
82  pageLabelInfo = nullptr;
83  form = nullptr;
84  optContent = nullptr;
87  destNameTree = nullptr;
88  embeddedFileNameTree = nullptr;
89  jsNameTree = nullptr;
90  viewerPrefs = nullptr;
91  structTreeRoot = nullptr;
92 
93  pagesList = nullptr;
94  pagesRefList = nullptr;
95  attrsList = nullptr;
96  kidsIdxList = nullptr;
98 
99  Object catDict = xref->getCatalog();
100  if (!catDict.isDict()) {
101  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
102  ok = false;
103  return;
104  }
105  // get the AcroForm dictionary
106  acroForm = catDict.dictLookup("AcroForm");
107 
108  // read base URI
109  Object obj = catDict.getDict()->lookupEnsureEncryptedIfNeeded("URI");
110  if (obj.isDict()) {
111  Object obj2 = obj.getDict()->lookupEnsureEncryptedIfNeeded("Base");
112  if (obj2.isString()) {
113  baseURI = obj2.getString()->copy();
114  }
115  }
116 
117  // get the Optional Content dictionary
118  Object optContentProps = catDict.dictLookup("OCProperties");
119  if (optContentProps.isDict()) {
120  optContent = new OCGs(&optContentProps, xref);
121  if (!optContent->isOk()) {
122  delete optContent;
123  optContent = nullptr;
124  }
125  }
126 
127  // actions
128  additionalActions = catDict.dictLookupNF("AA").copy();
129 
130  // get the ViewerPreferences dictionary
131  viewerPreferences = catDict.dictLookup("ViewerPreferences");
132 }
133 
135 {
136  delete kidsIdxList;
137  if (attrsList) {
138  std::vector<PageAttrs *>::iterator it;
139  for (it = attrsList->begin(); it != attrsList->end(); ++it) {
140  delete *it;
141  }
142  delete attrsList;
143  }
144  delete pagesRefList;
145  delete pagesList;
146  delete destNameTree;
147  delete embeddedFileNameTree;
148  delete jsNameTree;
149  if (baseURI) {
150  delete baseURI;
151  }
152  delete pageLabelInfo;
153  delete form;
154  delete optContent;
155  delete viewerPrefs;
156  delete structTreeRoot;
157 }
158 
160 {
161  catalogLocker();
162  if (metadata.isNone()) {
163  Object catDict = xref->getCatalog();
164  if (catDict.isDict()) {
165  metadata = catDict.dictLookup("Metadata");
166  } else {
167  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
169  }
170  }
171 
172  if (!metadata.isStream()) {
173  return nullptr;
174  }
175  Object obj = metadata.streamGetDict()->lookup("Subtype");
176  if (!obj.isName("XML")) {
177  error(errSyntaxWarning, -1, "Unknown Metadata type: '{0:s}'", obj.isName() ? obj.getName() : "???");
178  }
179  GooString *s = new GooString();
182  return s;
183 }
184 
185 Page *Catalog::getPage(int i)
186 {
187  if (i < 1)
188  return nullptr;
189 
190  catalogLocker();
191  if (std::size_t(i) > pages.size()) {
192  bool cached = cachePageTree(i);
193  if (cached == false) {
194  return nullptr;
195  }
196  }
197  return pages[i - 1].first.get();
198 }
199 
201 {
202  if (i < 1)
203  return nullptr;
204 
205  catalogLocker();
206  if (std::size_t(i) > pages.size()) {
207  bool cached = cachePageTree(i);
208  if (cached == false) {
209  return nullptr;
210  }
211  }
212  return &pages[i - 1].second;
213 }
214 
216 {
217  if (pagesList == nullptr) {
218 
219  Ref pagesRef;
220 
221  Object catDict = xref->getCatalog();
222 
223  if (catDict.isDict()) {
224  const Object &pagesDictRef = catDict.dictLookupNF("Pages");
225  if (pagesDictRef.isRef() && pagesDictRef.getRefNum() >= 0 && pagesDictRef.getRefNum() < xref->getNumObjects()) {
226  pagesRef = pagesDictRef.getRef();
227  } else {
228  error(errSyntaxError, -1, "Catalog dictionary does not contain a valid \"Pages\" entry");
229  return false;
230  }
231  } else {
232  error(errSyntaxError, -1, "Could not find catalog dictionary");
233  return false;
234  }
235 
236  Object obj = catDict.dictLookup("Pages");
237  // This should really be isDict("Pages"), but I've seen at least one
238  // PDF file where the /Type entry is missing.
239  if (!obj.isDict()) {
240  error(errSyntaxError, -1, "Top-level pages object is wrong type ({0:s})", obj.getTypeName());
241  return false;
242  }
243 
244  pages.clear();
245  attrsList = new std::vector<PageAttrs *>();
246  attrsList->push_back(new PageAttrs(nullptr, obj.getDict()));
247  pagesList = new std::vector<Object>();
248  pagesList->push_back(std::move(obj));
249  pagesRefList = new std::vector<Ref>();
250  pagesRefList->push_back(pagesRef);
251  kidsIdxList = new std::vector<int>();
252  kidsIdxList->push_back(0);
253  }
254 
255  while (true) {
256 
257  if (std::size_t(page) <= pages.size())
258  return true;
259 
260  if (pagesList->empty())
261  return false;
262 
263  Object kids = pagesList->back().dictLookup("Kids");
264  if (!kids.isArray()) {
265  error(errSyntaxError, -1, "Kids object (page {0:uld}) is wrong type ({1:s})", pages.size() + 1, kids.getTypeName());
266  return false;
267  }
268 
269  int kidsIdx = kidsIdxList->back();
270  if (kidsIdx >= kids.arrayGetLength()) {
271  pagesList->pop_back();
272  pagesRefList->pop_back();
273  delete attrsList->back();
274  attrsList->pop_back();
275  kidsIdxList->pop_back();
276  if (!kidsIdxList->empty())
277  kidsIdxList->back()++;
278  continue;
279  }
280 
281  const Object &kidRef = kids.arrayGetNF(kidsIdx);
282  if (!kidRef.isRef()) {
283  error(errSyntaxError, -1, "Kid object (page {0:uld}) is not an indirect reference ({1:s})", pages.size() + 1, kidRef.getTypeName());
284  return false;
285  }
286 
287  bool loop = false;
288  ;
289  for (const Ref &pageRef : *pagesRefList) {
290  if (pageRef.num == kidRef.getRefNum()) {
291  loop = true;
292  break;
293  }
294  }
295  if (loop) {
296  error(errSyntaxError, -1, "Loop in Pages tree");
297  kidsIdxList->back()++;
298  continue;
299  }
300 
301  Object kid = kids.arrayGet(kidsIdx);
302  if (kid.isDict("Page") || (kid.isDict() && !kid.getDict()->hasKey("Kids"))) {
303  PageAttrs *attrs = new PageAttrs(attrsList->back(), kid.getDict());
304  auto p = std::make_unique<Page>(doc, pages.size() + 1, std::move(kid), kidRef.getRef(), attrs, form);
305  if (!p->isOk()) {
306  error(errSyntaxError, -1, "Failed to create page (page {0:uld})", pages.size() + 1);
307  return false;
308  }
309 
310  if (pages.size() >= std::size_t(numPages)) {
311  error(errSyntaxError, -1, "Page count in top-level pages object is incorrect");
312  return false;
313  }
314 
315  pages.emplace_back(std::move(p), kidRef.getRef());
316 
317  kidsIdxList->back()++;
318 
319  // This should really be isDict("Pages"), but I've seen at least one
320  // PDF file where the /Type entry is missing.
321  } else if (kid.isDict()) {
322  attrsList->push_back(new PageAttrs(attrsList->back(), kid.getDict()));
323  pagesRefList->push_back(kidRef.getRef());
324  pagesList->push_back(std::move(kid));
325  kidsIdxList->push_back(0);
326  } else {
327  error(errSyntaxError, -1, "Kid object (page {0:uld}) is wrong type ({1:s})", pages.size() + 1, kid.getTypeName());
328  kidsIdxList->back()++;
329  }
330  }
331 
332  return false;
333 }
334 
335 int Catalog::findPage(const Ref pageRef)
336 {
337  int i;
338 
339  for (i = 0; i < getNumPages(); ++i) {
340  Ref *ref = getPageRef(i + 1);
341  if (ref != nullptr && *ref == pageRef)
342  return i + 1;
343  }
344  return 0;
345 }
346 
347 std::unique_ptr<LinkDest> Catalog::findDest(const GooString *name)
348 {
349  // try named destination dictionary then name tree
350  if (getDests()->isDict()) {
351  Object obj1 = getDests()->dictLookup(name->c_str());
352  return createLinkDest(&obj1);
353  }
354 
355  catalogLocker();
356  Object obj2 = getDestNameTree()->lookup(name);
357  return createLinkDest(&obj2);
358 }
359 
360 std::unique_ptr<LinkDest> Catalog::createLinkDest(Object *obj)
361 {
362  std::unique_ptr<LinkDest> dest;
363  if (obj->isArray()) {
364  dest = std::make_unique<LinkDest>(obj->getArray());
365  } else if (obj->isDict()) {
366  Object obj2 = obj->dictLookup("D");
367  if (obj2.isArray())
368  dest = std::make_unique<LinkDest>(obj2.getArray());
369  else
370  error(errSyntaxWarning, -1, "Bad named destination value");
371  } else {
372  error(errSyntaxWarning, -1, "Bad named destination value");
373  }
374  if (dest && !dest->isOk()) {
375  dest.reset();
376  }
377 
378  return dest;
379 }
380 
382 {
383  Object *obj;
384 
385  obj = getDests();
386  if (!obj->isDict()) {
387  return 0;
388  }
389  return obj->dictGetLength();
390 }
391 
392 const char *Catalog::getDestsName(int i)
393 {
394  Object *obj;
395 
396  obj = getDests();
397  if (!obj->isDict()) {
398  return nullptr;
399  }
400  return obj->dictGetKey(i);
401 }
402 
403 std::unique_ptr<LinkDest> Catalog::getDestsDest(int i)
404 {
405  Object *obj = getDests();
406  if (!obj->isDict()) {
407  return nullptr;
408  }
409  Object obj1 = obj->dictGetVal(i);
410  return createLinkDest(&obj1);
411 }
412 
413 std::unique_ptr<LinkDest> Catalog::getDestNameTreeDest(int i)
414 {
415  Object obj;
416 
417  catalogLocker();
419  if (aux) {
420  obj = aux->fetch(xref);
421  }
422  return createLinkDest(&obj);
423 }
424 
426 {
427  catalogLocker();
429  FileSpec *embeddedFile = nullptr;
430  if (obj->isRef()) {
431  Object fsDict = obj->fetch(xref);
432  embeddedFile = new FileSpec(&fsDict);
433  } else if (obj->isDict()) {
434  embeddedFile = new FileSpec(obj);
435  } else {
436  Object null;
437  embeddedFile = new FileSpec(&null);
438  }
439  return embeddedFile;
440 }
441 
443 {
445  for (int i = 0; i < ef->numEntries(); ++i) {
446  if (fileName == ef->getName(i)->toStr())
447  return true;
448  }
449  return false;
450 }
451 
453 {
454  catalogLocker();
455 
457  const Ref fileSpecRef = xref->addIndirectObject(&fileSpecObj);
458 
459  Object catDict = xref->getCatalog();
460  Ref namesObjRef;
461  Object namesObj = catDict.getDict()->lookup("Names", &namesObjRef);
462  if (!namesObj.isDict()) {
463  // Need to create the names Dict
464  catDict.dictSet("Names", Object(new Dict(xref)));
465  namesObj = catDict.getDict()->lookup("Names");
466 
467  // Trigger getting the names dict again when needed
468  names = Object();
469  }
470 
471  Dict *namesDict = namesObj.getDict();
472 
473  // We create a new EmbeddedFiles nametree, this replaces the existing one (if any), but it's not a problem
474  Object embeddedFilesObj = Object(new Dict(xref));
475  const Ref embeddedFilesRef = xref->addIndirectObject(&embeddedFilesObj);
476 
477  Array *embeddedFilesNamesArray = new Array(xref);
478 
479  // This flattens out the existing EmbeddedFiles nametree (if any), should not be a problem
481  bool fileAlreadyAdded = false;
482  for (int i = 0; i < ef->numEntries(); ++i) {
483  GooString *efNameI = ef->getName(i);
484 
485  // we need to add the file if it has not been added yet and the name is smaller or equal lexicographically
486  // than the current item
487  const bool sameFileName = fileName == efNameI->toStr();
488  const bool addFile = !fileAlreadyAdded && (sameFileName || fileName < efNameI->toStr());
489  if (addFile) {
490  // If the new name is smaller lexicographically than an existing file add it in its correct position
491  embeddedFilesNamesArray->add(Object(new GooString(fileName)));
492  embeddedFilesNamesArray->add(Object(fileSpecRef));
493  fileAlreadyAdded = true;
494  }
495  if (sameFileName) {
496  // If the new name is the same lexicographically than an existing file then don't add the existing file (i.e. replace)
497  continue;
498  }
499  embeddedFilesNamesArray->add(Object(efNameI->copy()));
500  embeddedFilesNamesArray->add(ef->getValue(i)->copy());
501  }
502 
503  if (!fileAlreadyAdded) {
504  // The new file is bigger lexicographically than the existing ones
505  embeddedFilesNamesArray->add(Object(new GooString(fileName)));
506  embeddedFilesNamesArray->add(Object(fileSpecRef));
507  }
508 
509  embeddedFilesObj.dictSet("Names", Object(embeddedFilesNamesArray));
510  namesDict->set("EmbeddedFiles", Object(embeddedFilesRef));
511 
512  if (namesObjRef != Ref::INVALID()) {
513  xref->setModifiedObject(&namesObj, namesObjRef);
514  } else {
515  xref->setModifiedObject(&catDict, { xref->getRootNum(), xref->getRootGen() });
516  }
517 
518  // recreate Nametree on next call that uses it
519  delete embeddedFileNameTree;
520  embeddedFileNameTree = nullptr;
521 }
522 
524 {
525  Object obj;
526  // getJSNameTree()->getValue(i) returns a shallow copy of the object so we
527  // do not need to free it
528  catalogLocker();
530  if (aux) {
531  obj = aux->fetch(xref);
532  }
533 
534  if (!obj.isDict()) {
535  return nullptr;
536  }
537  Object obj2 = obj.dictLookup("S");
538  if (!obj2.isName()) {
539  return nullptr;
540  }
541  if (strcmp(obj2.getName(), "JavaScript")) {
542  return nullptr;
543  }
544  obj2 = obj.dictLookup("JS");
545  GooString *js = nullptr;
546  if (obj2.isString()) {
547  js = new GooString(obj2.getString());
548  } else if (obj2.isStream()) {
549  Stream *stream = obj2.getStream();
550  js = new GooString();
551  stream->fillGooString(js);
552  }
553  return js;
554 }
555 
557 {
558 
559  catalogLocker();
560  if (pageMode == pageModeNull) {
561 
563 
564  Object catDict = xref->getCatalog();
565  if (!catDict.isDict()) {
566  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
567  return pageMode;
568  }
569 
570  Object obj = catDict.dictLookup("PageMode");
571  if (obj.isName()) {
572  if (obj.isName("UseNone"))
574  else if (obj.isName("UseOutlines"))
576  else if (obj.isName("UseThumbs"))
578  else if (obj.isName("FullScreen"))
580  else if (obj.isName("UseOC"))
582  else if (obj.isName("UseAttachments"))
584  }
585  }
586  return pageMode;
587 }
588 
590 {
591 
592  catalogLocker();
593  if (pageLayout == pageLayoutNull) {
594 
596 
597  Object catDict = xref->getCatalog();
598  if (!catDict.isDict()) {
599  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
600  return pageLayout;
601  }
602 
604  Object obj = catDict.dictLookup("PageLayout");
605  if (obj.isName()) {
606  if (obj.isName("SinglePage"))
608  if (obj.isName("OneColumn"))
610  if (obj.isName("TwoColumnLeft"))
612  if (obj.isName("TwoColumnRight"))
614  if (obj.isName("TwoPageLeft"))
616  if (obj.isName("TwoPageRight"))
618  }
619  }
620  return pageLayout;
621 }
622 
624 {
625  size = 0;
626  length = 0;
627  entries = nullptr;
628 }
629 
631 {
632  int i;
633 
634  for (i = 0; i < length; i++)
635  delete entries[i];
636 
637  gfree(entries);
638 }
639 
641 {
642  if (!array->getString(index, &name)) {
643  Object aux = array->get(index);
644  if (aux.isString()) {
645  name.append(aux.getString());
646  } else
647  error(errSyntaxError, -1, "Invalid page tree");
648  }
649  value = array->getNF(index + 1).copy();
650 }
651 
653 
655 {
656  if (length == size) {
657  if (length == 0) {
658  size = 8;
659  } else {
660  size *= 2;
661  }
662  entries = (Entry **)grealloc(entries, sizeof(Entry *) * size);
663  }
664 
665  entries[length] = entry;
666  ++length;
667 }
668 
669 int NameTree::Entry::cmpEntry(const void *voidEntry, const void *voidOtherEntry)
670 {
671  Entry *entry = *(NameTree::Entry **)voidEntry;
672  Entry *otherEntry = *(NameTree::Entry **)voidOtherEntry;
673 
674  return entry->name.cmp(&otherEntry->name);
675 }
676 
677 void NameTree::init(XRef *xrefA, Object *tree)
678 {
679  xref = xrefA;
680  std::set<int> seen;
681  parse(tree, seen);
682  if (entries && length > 0) {
683  qsort(entries, length, sizeof(Entry *), Entry::cmpEntry);
684  }
685 }
686 
687 void NameTree::parse(const Object *tree, std::set<int> &seen)
688 {
689  if (!tree->isDict())
690  return;
691 
692  // leaf node
693  Object names = tree->dictLookup("Names");
694  if (names.isArray()) {
695  for (int i = 0; i < names.arrayGetLength(); i += 2) {
697 
698  entry = new Entry(names.getArray(), i);
699  addEntry(entry);
700  }
701  }
702 
703  // root or intermediate node
704  Ref ref;
705  const Object kids = tree->getDict()->lookup("Kids", &ref);
706  if (ref != Ref::INVALID()) {
707  const int numObj = ref.num;
708  if (seen.find(numObj) != seen.end()) {
709  error(errSyntaxError, -1, "loop in NameTree (numObj: {0:d})", numObj);
710  return;
711  }
712  seen.insert(numObj);
713  }
714  if (kids.isArray()) {
715  for (int i = 0; i < kids.arrayGetLength(); ++i) {
716  const Object kid = kids.getArray()->get(i, &ref);
717  if (ref != Ref::INVALID()) {
718  const int numObj = ref.num;
719  if (seen.find(numObj) != seen.end()) {
720  error(errSyntaxError, -1, "loop in NameTree (numObj: {0:d})", numObj);
721  continue;
722  }
723  seen.insert(numObj);
724  }
725  if (kid.isDict())
726  parse(&kid, seen);
727  }
728  }
729 }
730 
731 int NameTree::Entry::cmp(const void *voidKey, const void *voidEntry)
732 {
733  GooString *key = (GooString *)voidKey;
734  Entry *entry = *(NameTree::Entry **)voidEntry;
735 
736  return key->cmp(&entry->name);
737 }
738 
740 {
741  Entry **entry;
742 
743  entry = (Entry **)bsearch(name, entries, length, sizeof(Entry *), Entry::cmp);
744  if (entry != nullptr) {
745  return (*entry)->value.fetch(xref);
746  } else {
747  error(errSyntaxError, -1, "failed to look up ({0:s})", name->c_str());
748  return Object(objNull);
749  }
750 }
751 
753 {
754  if (index < length) {
755  return &entries[index]->value;
756  } else {
757  return nullptr;
758  }
759 }
760 
762 {
763  if (index < length) {
764  return &entries[index]->name;
765  } else {
766  return nullptr;
767  }
768 }
769 
771 {
772  char *end;
773 
774  PageLabelInfo *pli = getPageLabelInfo();
775  if (pli != nullptr) {
776  if (!pli->labelToIndex(label, index))
777  return false;
778  } else {
779  *index = strtol(label->c_str(), &end, 10) - 1;
780  if (*end != '\0')
781  return false;
782  }
783 
784  if (*index < 0 || *index >= getNumPages())
785  return false;
786 
787  return true;
788 }
789 
791 {
792  char buffer[32];
793 
794  if (index < 0 || index >= getNumPages())
795  return false;
796 
797  PageLabelInfo *pli = getPageLabelInfo();
798  if (pli != nullptr) {
799  return pli->indexToLabel(index, label);
800  } else {
801  snprintf(buffer, sizeof(buffer), "%d", index + 1);
802  label->append(buffer);
803  return true;
804  }
805 }
806 
808 {
809  catalogLocker();
810  if (numPages == -1) {
811  Object catDict = xref->getCatalog();
812  if (!catDict.isDict()) {
813  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
814  return 0;
815  }
816  Object pagesDict = catDict.dictLookup("Pages");
817 
818  // This should really be isDict("Pages"), but I've seen at least one
819  // PDF file where the /Type entry is missing.
820  if (!pagesDict.isDict()) {
821  error(errSyntaxError, -1, "Top-level pages object is wrong type ({0:s})", pagesDict.getTypeName());
822  return 0;
823  }
824 
825  Object obj = pagesDict.dictLookup("Count");
826  // some PDF files actually use real numbers here ("/Count 9.0")
827  if (!obj.isNum()) {
828  if (pagesDict.dictIs("Page")) {
829  const Object &pageRootRef = catDict.dictLookupNF("Pages");
830 
831  error(errSyntaxError, -1, "Pages top-level is a single Page. The document is malformed, trying to recover...");
832 
833  Dict *pageDict = pagesDict.getDict();
834  if (pageRootRef.isRef()) {
835  const Ref pageRef = pageRootRef.getRef();
836  auto p = std::make_unique<Page>(doc, 1, std::move(pagesDict), pageRef, new PageAttrs(nullptr, pageDict), form);
837  if (p->isOk()) {
838  pages.emplace_back(std::move(p), pageRef);
839 
840  numPages = 1;
841  } else {
842  numPages = 0;
843  }
844  } else {
845  numPages = 0;
846  }
847  } else {
848  error(errSyntaxError, -1, "Page count in top-level pages object is wrong type ({0:s})", obj.getTypeName());
849  numPages = 0;
850  }
851  } else {
852  numPages = (int)obj.getNum();
853  if (numPages <= 0) {
854  error(errSyntaxError, -1, "Invalid page count {0:d}", numPages);
855  numPages = 0;
856  } else if (numPages > xref->getNumObjects()) {
857  error(errSyntaxError, -1, "Page count ({0:d}) larger than number of objects ({1:d})", numPages, xref->getNumObjects());
858  numPages = 0;
859  }
860  }
861  }
862 
863  return numPages;
864 }
865 
867 {
868  catalogLocker();
869  if (!pageLabelInfo) {
870  Object catDict = xref->getCatalog();
871  if (!catDict.isDict()) {
872  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
873  return nullptr;
874  }
875 
876  Object obj = catDict.dictLookup("PageLabels");
877  if (obj.isDict()) {
878  pageLabelInfo = new PageLabelInfo(&obj, getNumPages());
879  }
880  }
881 
882  return pageLabelInfo;
883 }
884 
886 {
887  catalogLocker();
888  if (!structTreeRoot) {
890  if (!catalog.isDict()) {
891  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catalog.getTypeName());
892  return nullptr;
893  }
894 
895  Object root = catalog.dictLookup("StructTreeRoot");
896  if (root.isDict("StructTreeRoot")) {
897  structTreeRoot = new StructTreeRoot(doc, root.getDict());
898  }
899  }
900  return structTreeRoot;
901 }
902 
903 unsigned int Catalog::getMarkInfo()
904 {
905  if (markInfo == markInfoNull) {
906  markInfo = 0;
907 
908  catalogLocker();
909  Object catDict = xref->getCatalog();
910 
911  if (catDict.isDict()) {
912  Object markInfoDict = catDict.dictLookup("MarkInfo");
913  if (markInfoDict.isDict()) {
914  Object value = markInfoDict.dictLookup("Marked");
915  if (value.isBool()) {
916  if (value.getBool()) {
917  markInfo |= markInfoMarked;
918  }
919  } else if (!value.isNull()) {
920  error(errSyntaxError, -1, "Marked object is wrong type ({0:s})", value.getTypeName());
921  }
922 
923  value = markInfoDict.dictLookup("Suspects");
924  if (value.isBool() && value.getBool())
925  markInfo |= markInfoSuspects;
926  else if (!value.isNull())
927  error(errSyntaxError, -1, "Suspects object is wrong type ({0:s})", value.getTypeName());
928 
929  value = markInfoDict.dictLookup("UserProperties");
930  if (value.isBool() && value.getBool())
931  markInfo |= markInfoUserProperties;
932  else if (!value.isNull())
933  error(errSyntaxError, -1, "UserProperties object is wrong type ({0:s})", value.getTypeName());
934  } else if (!markInfoDict.isNull()) {
935  error(errSyntaxError, -1, "MarkInfo object is wrong type ({0:s})", markInfoDict.getTypeName());
936  }
937  } else {
938  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
939  }
940  }
941  return markInfo;
942 }
943 
945 {
946  catalogLocker();
947  if (outline.isNone()) {
948  Object catDict = xref->getCatalog();
949  if (catDict.isDict()) {
950  outline = catDict.dictLookup("Outlines");
951  } else {
952  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
953  outline.setToNull();
954  }
955  }
956 
957  return &outline;
958 }
959 
961 {
962  catalogLocker();
963  if (dests.isNone()) {
964  Object catDict = xref->getCatalog();
965  if (catDict.isDict()) {
966  dests = catDict.dictLookup("Dests");
967  } else {
968  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
969  dests.setToNull();
970  }
971  }
972 
973  return &dests;
974 }
975 
977 {
978  Object xfa;
979  FormType res = NoForm;
980 
981  if (acroForm.isDict()) {
982  xfa = acroForm.dictLookup("XFA");
983  if (xfa.isStream() || xfa.isArray()) {
984  res = XfaForm;
985  } else {
986  res = AcroForm;
987  }
988  }
989 
990  return res;
991 }
992 
994 {
995  catalogLocker();
996  if (!form) {
997  if (acroForm.isDict()) {
998  form = new Form(doc, &acroForm);
999  // perform form-related loading after all widgets have been loaded
1000  form->postWidgetsLoad();
1001  }
1002  }
1003 
1004  return form;
1005 }
1006 
1007 void Catalog::addFormToAcroForm(const Ref formRef)
1008 {
1009  catalogLocker();
1010 
1011  Object catDict = xref->getCatalog();
1012  Ref acroFormRef;
1013  acroForm = catDict.getDict()->lookup("AcroForm", &acroFormRef);
1014 
1015  if (!acroForm.isDict()) {
1016  // none there yet, need to create a new fields dict
1017  Object newForm = Object(new Dict(xref));
1018  newForm.dictSet("SigFlags", Object(3));
1019 
1020  Array *fieldArray = new Array(xref);
1021  fieldArray->add(Object(formRef));
1022  newForm.dictSet("Fields", Object(fieldArray));
1023 
1024  Ref newRef = xref->addIndirectObject(&newForm);
1025  catDict.dictSet("AcroForm", Object(newRef));
1026  acroForm = catDict.getDict()->lookup("AcroForm");
1027  } else {
1028  // append to field array
1029  Ref fieldRef;
1030  Object fieldArray = acroForm.getDict()->lookup("Fields", &fieldRef);
1031  fieldArray.getArray()->add(Object(formRef));
1032  }
1033 
1034  if (acroFormRef != Ref::INVALID()) {
1035  xref->setModifiedObject(&acroForm, acroFormRef);
1036  } else {
1037  xref->setModifiedObject(&catDict, { xref->getRootNum(), xref->getRootGen() });
1038  }
1039 }
1040 
1042 {
1043  catalogLocker();
1044 
1045  Object catDict = xref->getCatalog();
1046  Ref acroFormRef;
1047  acroForm = catDict.getDict()->lookup("AcroForm", &acroFormRef);
1048 
1049  if (acroForm.isDict()) {
1050  // remove from field array
1051  Ref fieldRef;
1052  Object fieldArrayO = acroForm.getDict()->lookup("Fields", &fieldRef);
1053  Array *fieldArray = fieldArrayO.getArray();
1054  for (int i = 0; i < fieldArray->getLength(); ++i) {
1055  const Object &o = fieldArray->getNF(i);
1056  if (o.isRef() && o.getRef() == formRef) {
1057  fieldArray->remove(i);
1058  break;
1059  }
1060  }
1061 
1062  xref->setModifiedObject(&acroForm, acroFormRef);
1063  }
1064 }
1065 
1067 {
1068  catalogLocker();
1069  if (!viewerPrefs) {
1070  if (viewerPreferences.isDict()) {
1071  viewerPrefs = new ViewerPreferences(viewerPreferences.getDict());
1072  }
1073  }
1074 
1075  return viewerPrefs;
1076 }
1077 
1079 {
1080  if (names.isNone()) {
1081  Object catDict = xref->getCatalog();
1082  if (catDict.isDict()) {
1083  names = catDict.dictLookup("Names");
1084  } else {
1085  error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
1086  names.setToNull();
1087  }
1088  }
1089 
1090  return &names;
1091 }
1092 
1094 {
1095  if (!destNameTree) {
1096 
1097  destNameTree = new NameTree();
1098 
1099  if (getNames()->isDict()) {
1100  Object obj = getNames()->dictLookup("Dests");
1101  destNameTree->init(xref, &obj);
1102  }
1103  }
1104 
1105  return destNameTree;
1106 }
1107 
1109 {
1110  if (!embeddedFileNameTree) {
1111 
1112  embeddedFileNameTree = new NameTree();
1113 
1114  if (getNames()->isDict()) {
1115  Object obj = getNames()->dictLookup("EmbeddedFiles");
1116  embeddedFileNameTree->init(xref, &obj);
1117  }
1118  }
1119 
1120  return embeddedFileNameTree;
1121 }
1122 
1124 {
1125  if (!jsNameTree) {
1126 
1127  jsNameTree = new NameTree();
1128 
1129  if (getNames()->isDict()) {
1130  Object obj = getNames()->dictLookup("JavaScript");
1131  jsNameTree->init(xref, &obj);
1132  }
1133  }
1134 
1135  return jsNameTree;
1136 }
1137 
1139 {
1140  Object additionalActionsObject = additionalActions.fetch(doc->getXRef());
1141  if (additionalActionsObject.isDict()) {
1142  const char *key = (type == actionCloseDocument ? "WC"
1143  : type == actionSaveDocumentStart ? "WS"
1144  : type == actionSaveDocumentFinish ? "DS"
1145  : type == actionPrintDocumentStart ? "WP"
1146  : type == actionPrintDocumentFinish ? "DP"
1147  : nullptr);
1148 
1149  Object actionObject = additionalActionsObject.dictLookup(key);
1150  if (actionObject.isDict())
1151  return LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI());
1152  }
1153  return nullptr;
1154 }
void *__cdecl bsearch(void const *_Key, void const *_Base, size_t _NumOfElements, size_t _SizeOfElements, _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction)
long __cdecl strtol(char const *_String, char **_EndPtr, int _Radix)
#define type(a)
Definition: aptex-macros.h:171
#define name
Definition: Array.h:29
void remove(int i)
Definition: Array.cc:66
Object * getNF(int i, Object *obj)
Definition: Array.cc:65
void add(Object *elem)
Definition: Array.cc:41
Object * get(int i, Object *obj, int recursion=0)
Definition: Array.cc:54
int getLength()
Definition: Array.h:48
AcroForm * getForm()
Definition: Catalog.h:91
std::unique_ptr< LinkDest > createLinkDest(Object *obj)
Definition: Catalog.cc:360
Object names
Definition: Catalog.h:277
PageLabelInfo * getPageLabelInfo()
Definition: Catalog.cc:866
NameTree * destNameTree
Definition: Catalog.h:278
Ref * getPageRef(int i)
Definition: Catalog.cc:316
Catalog(PDFDoc *docA)
Definition: Catalog.cc:148
Object structTreeRoot
Definition: Catalog.h:136
int numPages
Definition: Catalog.h:131
Object * getDests()
Definition: Catalog.h:83
Object viewerPreferences
Definition: Catalog.h:287
GooString * getJS(int i)
Definition: Catalog.cc:523
std::vector< PageAttrs * > * attrsList
Definition: Catalog.h:271
GString * getBaseURI()
Definition: Catalog.h:66
NameTree * getDestNameTree()
Definition: Catalog.cc:1093
Object metadata
Definition: Catalog.h:135
NameTree * getJSNameTree()
Definition: Catalog.cc:1123
NameTree * jsNameTree
Definition: Catalog.h:280
bool labelToIndex(GooString *label, int *index)
Definition: Catalog.cc:770
@ markInfoNull
Definition: Catalog.h:145
unsigned int markInfo
Definition: Catalog.h:284
Object * getOutline()
Definition: Catalog.h:87
FormType
Definition: Catalog.h:214
PDFDoc * doc
Definition: Catalog.h:123
Object * getNames()
Definition: Catalog.cc:1078
OCGs * optContent
Definition: Catalog.h:288
PageLayout pageLayout
Definition: Catalog.h:292
FormType getFormType()
Definition: Catalog.cc:976
int numDests()
Definition: Catalog.cc:381
bool cachePageTree(int page)
Definition: Catalog.cc:215
Page * getPage(int i)
Definition: Catalog.cc:300
PageMode pageMode
Definition: Catalog.h:291
int getNumPages()
Definition: Catalog.h:53
std::vector< Ref > * pagesRefList
Definition: Catalog.h:270
void addFormToAcroForm(const Ref formRef)
Definition: Catalog.cc:1007
Object acroForm
Definition: Catalog.h:138
NameTree * embeddedFileNameTree
Definition: Catalog.h:279
void addEmbeddedFile(GooFile *file, const std::string &fileName)
Definition: Catalog.cc:452
bool hasEmbeddedFile(const std::string &fileName)
Definition: Catalog.cc:442
PageLabelInfo * pageLabelInfo
Definition: Catalog.h:290
std::vector< Object > * pagesList
Definition: Catalog.h:269
PageLayout
Definition: Catalog.h:236
@ pageLayoutOneColumn
Definition: Catalog.h:239
@ pageLayoutTwoColumnLeft
Definition: Catalog.h:240
@ pageLayoutNone
Definition: Catalog.h:237
@ pageLayoutSinglePage
Definition: Catalog.h:238
@ pageLayoutTwoPageRight
Definition: Catalog.h:243
@ pageLayoutNull
Definition: Catalog.h:244
@ pageLayoutTwoColumnRight
Definition: Catalog.h:241
@ pageLayoutTwoPageLeft
Definition: Catalog.h:242
void removeFormFromAcroForm(const Ref formRef)
Definition: Catalog.cc:1041
std::unique_ptr< LinkDest > getDestsDest(int i)
Definition: Catalog.cc:403
Object * getStructTreeRoot()
Definition: Catalog.h:73
NameTree * getEmbeddedFileNameTree()
Definition: Catalog.cc:1108
PageMode getPageMode()
Definition: Catalog.cc:556
FileSpec * embeddedFile(int i)
Definition: Catalog.cc:425
bool indexToLabel(int index, GooString *label)
Definition: Catalog.cc:790
const char * getDestsName(int i)
Definition: Catalog.cc:392
Object additionalActions
Definition: Catalog.h:293
unsigned int getMarkInfo()
Definition: Catalog.cc:903
DocumentAdditionalActionsType
Definition: Catalog.h:252
GString * readMetadata()
Definition: Catalog.cc:345
Object viewerPrefs
Definition: Catalog.h:144
PageLayout getPageLayout()
Definition: Catalog.cc:589
GBool ok
Definition: Catalog.h:145
XRef * xref
Definition: Catalog.h:124
std::vector< int > * kidsIdxList
Definition: Catalog.h:272
std::unique_ptr< LinkAction > getAdditionalAction(DocumentAdditionalActionsType type)
Definition: Catalog.cc:1138
Object * getViewerPreferences()
Definition: Catalog.h:119
AcroForm * form
Definition: Catalog.h:140
int findPage(int num, int gen)
Definition: Catalog.cc:370
PageMode
Definition: Catalog.h:226
@ pageModeThumbs
Definition: Catalog.h:229
@ pageModeFullScreen
Definition: Catalog.h:230
@ pageModeOC
Definition: Catalog.h:231
@ pageModeNull
Definition: Catalog.h:233
@ pageModeAttach
Definition: Catalog.h:232
@ pageModeOutlines
Definition: Catalog.h:228
@ pageModeNone
Definition: Catalog.h:227
std::unique_ptr< LinkDest > getDestNameTreeDest(int i)
Definition: Catalog.cc:413
LinkDest * findDest(GString *name)
Definition: Catalog.cc:393
GString * baseURI
Definition: Catalog.h:134
~Catalog()
Definition: Catalog.cc:260
Definition: Dict.h:29
Object lookupEnsureEncryptedIfNeeded(const char *key) const
Definition: Dict.cc:186
Object * lookup(const char *key, Object *obj, int recursion=0)
Definition: Dict.cc:122
void set(const char *key, Object &&val)
Definition: Dict.cc:142
bool hasKey(const char *key) const
Definition: Dict.cc:233
static Object newFileSpecObject(XRef *xref, GooFile *file, const std::string &fileName)
Definition: FileSpec.cc:171
Definition: Form.h:640
GString * copy()
Definition: GString.h:42
Definition: gfile.h:115
const std::string & toStr() const
Definition: GooString.h:72
GooString * copy() const
Definition: GooString.h:101
GooString * append(char c)
Definition: GooString.h:161
static LinkAction * parseAction(Object *obj, GString *baseURI=NULL)
Definition: Link.cc:41
~NameTree()
Definition: Catalog.cc:630
GooString * getName(int i)
Definition: Catalog.cc:761
Object lookup(const GooString *name)
Definition: Catalog.cc:739
Entry ** entries
Definition: Catalog.h:98
void parse(const Object *tree, std::set< int > &seen)
Definition: Catalog.cc:687
int numEntries()
Definition: Catalog.h:78
void init(XRef *xref, Object *tree)
Definition: Catalog.cc:677
int length
Definition: Catalog.h:99
void addEntry(Entry *entry)
Definition: Catalog.cc:654
NameTree()
Definition: Catalog.cc:623
Object * getValue(int i)
Definition: Catalog.cc:752
XRef * xref
Definition: Catalog.h:97
int size
Definition: Catalog.h:99
bool isOk() const
Definition: Object.h:84
Ref getRef()
Definition: Object.h:163
GBool isDict()
Definition: Object.h:137
Object * dictLookup(const char *key, Object *obj, int recursion=0)
Definition: Object.h:266
void setToNull()
Definition: Object.h:259
Stream * getStream()
Definition: Object.h:162
GBool dictIs(const char *dictType)
Definition: Object.h:260
GBool isNum()
Definition: Object.h:132
Array * getArray()
Definition: Object.h:160
void streamClose()
Definition: Object.h:296
double getNum()
Definition: Object.h:157
Object * arrayGetNF(int i, Object *obj)
Definition: Object.h:245
GBool isName()
Definition: Object.h:134
const char * getTypeName()
Definition: Object.cc:156
GBool isStream()
Definition: Object.h:138
GBool isRef()
Definition: Object.h:139
Object * copy(Object *obj)
Definition: Object.cc:80
Object * fetch(XRef *xref, Object *obj, int recursion=0)
Definition: Object.cc:114
int dictGetLength()
Definition: Object.h:254
char * getName()
Definition: Object.h:159
GBool isNone()
Definition: Object.h:143
Dict * streamGetDict()
Definition: Object.h:317
int arrayGetLength()
Definition: Object.h:236
GBool isArray()
Definition: Object.h:136
GBool isString()
Definition: Object.h:133
GString * getString()
Definition: Object.h:158
Dict * getDict()
Definition: Object.h:161
char * dictGetKey(int i)
Definition: Object.h:272
Object * dictLookupNF(const char *key, Object *obj)
Definition: Object.h:269
void dictSet(const char *key, Object &&val)
Definition: Object.h:595
Object * dictGetVal(int i, Object *obj)
Definition: Object.h:275
GBool isNull()
Definition: Object.h:135
int getRefNum()
Definition: Object.h:164
Object * arrayGet(int i, Object *obj, int recursion=0)
Definition: Object.h:242
Definition: PDFDoc.h:38
XRef * getXRef()
Definition: PDFDoc.h:72
Catalog * getCatalog()
Definition: PDFDoc.h:75
Definition: Page.h:43
bool indexToLabel(int index, GooString *label) const
bool labelToIndex(GooString *label, int *index) const
Definition: Page.h:112
Definition: Stream.h:67
void fillGooString(GooString *s)
Definition: Stream.h:153
Definition: XRef.h:58
Ref addIndirectObject(const Object *o)
Definition: XRef.cc:1392
int getNumObjects()
Definition: XRef.h:102
int getRootGen()
Definition: XRef.h:112
void setModifiedObject(const Object *o, Ref r)
Definition: XRef.cc:1379
int getRootNum()
Definition: XRef.h:111
Object * getCatalog(Object *obj)
Definition: XRef.h:92
#define gfree(p)
Definition: dt2dv.c:326
int strcmp()
Definition: coll.cpp:143
#define error(a)
Definition: dviinfo.c:48
struct _Entry Entry
struct move_struct move
#define s
Definition: afcover.h:80
small capitals from c petite p
Definition: afcover.h:72
small capitals from c petite p scientific i
Definition: afcover.h:80
@ errSyntaxError
Definition: Error.h:25
@ errSyntaxWarning
Definition: Error.h:23
@ objNull
Definition: Object.h:53
static PDFDoc * doc
Definition: pdffonts.cc:89
static dest_entry * dests
Definition: pdfdoc.c:613
pdf_obj * entry
Definition: pdfdoc.c:64
static pdf_obj * catalog
Definition: pdfdoc.c:47
static str_llist_type * cached(kpathsea kpse, const_string key)
Definition: elt-dirs.c:82
#define string
Definition: ctangleboot.c:111
#define root
Definition: ctangleboot.c:69
#define dest
#define loop
Definition: tie.c:8
#define snprintf
Definition: snprintf.c:41
#define qsort
Definition: includes.h:72
struct array Array
@ aux
Definition: lyrics.c:59
#define size_t
Definition: glob.c:257
list names
Definition: fc-lang.py:151
def ref(x)
Definition: pdf-org.py:104
#define res(length)
Definition: picttoppm.c:287
static void addFile(const char *filename, const char *name, const char *source, UBool sourceTOC, UBool verbose)
Definition: pkg_gencmn.cpp:408
#define index(s, c)
Definition: plain2.h:351
struct stream_s stream
Definition: pts_fax.h:93
#define catalogLocker()
Definition: Catalog.cc:73
#define grealloc
Definition: basics.h:41
static int cmpEntry(const void *voidEntry, const void *voidOtherEntry)
Definition: Catalog.cc:669
Entry(Array *array, int index)
Definition: Catalog.cc:640
static int cmp(const void *key, const void *entry)
Definition: Catalog.cc:731
GooString name
Definition: Catalog.h:88
Object value
Definition: Catalog.h:89
Definition: Object.h:37
static constexpr Ref INVALID()
Definition: Object.h:94
Definition: utils.c:300
Definition: pdfdoc.c:607
Definition: filedef.h:30
Definition: mendex.h:20
Definition: mendex.h:14
Definition: pdfdoc.c:79
Definition: sh2.c:920
struct def_label label[1024]
Definition: t1part.c:286
#define key
Definition: tex2xindy.c:753
return() int(((double) *(font_tbl[cur_fnt].wtbl+(int)(*(font_tbl[cur_fnt].char_wi+(int)(ch - font_tbl[cur_fnt].char_f)% 256)))/(double)(1L<< 20)) *(double) font_tbl[cur_fnt].scale)
TT_Outline outline
Definition: ttf2pfb.c:167
const char * fileName
Definition: ugrep.cpp:52
Definition: obx.h:51
#define end(cp)
Definition: zic.c:71