1 //========================================================================
2 //
3 // Outline.cc
4 //
5 // Copyright 2002-2003 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 Marco Pesenti Gritti <mpg@redhat.com>
17 // Copyright (C) 2008, 2016-2019, 2021 Albert Astals Cid <aacid@kde.org>
18 // Copyright (C) 2009 Nick Jones <nick.jones@network-box.com>
19 // Copyright (C) 2016 Jason Crain <jason@aquaticape.us>
20 // Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
21 // 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
22 // Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
23 // Copyright (C) 2019, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
24 // Copyright (C) 2021 RM <rm+git@arcsin.org>
25 //
26 // To see a description of the changes please see the Changelog file that
27 // came with your tarball or type make ChangeLog if you are building from git
28 //
29 //========================================================================
30
31 #include <config.h>
32
33 #include "goo/gmem.h"
34 #include "goo/GooString.h"
35 #include "PDFDoc.h"
36 #include "XRef.h"
37 #include "Link.h"
38 #include "PDFDocEncoding.h"
39 #include "Outline.h"
40 #include "UTF.h"
41
42 //------------------------------------------------------------------------
43
Outline(Object * outlineObjA,XRef * xrefA,PDFDoc * docA)44 Outline::Outline(Object *outlineObjA, XRef *xrefA, PDFDoc *docA)
45 {
46 outlineObj = outlineObjA;
47 xref = xrefA;
48 doc = docA;
49 items = nullptr;
50 if (!outlineObj->isDict()) {
51 return;
52 }
53 const Object &first = outlineObj->dictLookupNF("First");
54 items = OutlineItem::readItemList(nullptr, &first, xref, doc);
55 }
56
~Outline()57 Outline::~Outline()
58 {
59 if (items) {
60 for (auto entry : *items) {
61 delete entry;
62 }
63 delete items;
64 }
65 }
66
insertChildHelper(const std::string & itemTitle,int destPageNum,unsigned int pos,Ref parentObjRef,PDFDoc * doc,XRef * xref,std::vector<OutlineItem * > & items)67 static void insertChildHelper(const std::string &itemTitle, int destPageNum, unsigned int pos, Ref parentObjRef, PDFDoc *doc, XRef *xref, std::vector<OutlineItem *> &items)
68 {
69 std::vector<OutlineItem *>::const_iterator it;
70 if (pos >= items.size()) {
71 it = items.end();
72 } else {
73 it = items.begin() + pos;
74 }
75
76 Array *a = new Array(xref);
77 Ref *pageRef = doc->getCatalog()->getPageRef(destPageNum);
78 if (pageRef != nullptr) {
79 a->add(Object(*pageRef));
80 } else {
81 // if the page obj doesn't exist put the page number
82 // PDF32000-2008 12.3.2.2 Para 2
83 // as if it's a "Remote-Go-To Actions"
84 // it's not strictly valid, but most viewers seem
85 // to handle it without crashing
86 // alternately, could put 0, or omit it
87 a->add(Object(destPageNum - 1));
88 }
89 a->add(Object(objName, "Fit"));
90
91 Object outlineItem = Object(new Dict(xref));
92
93 GooString *g = new GooString(itemTitle);
94 outlineItem.dictSet("Title", Object(g));
95 outlineItem.dictSet("Dest", Object(a));
96 outlineItem.dictSet("Count", Object(1));
97 outlineItem.dictAdd("Parent", Object(parentObjRef));
98
99 // add one to the main outline Object's count
100 Object parentObj = xref->fetch(parentObjRef);
101 int parentCount = parentObj.dictLookup("Count").getInt();
102 parentObj.dictSet("Count", Object(parentCount + 1));
103 xref->setModifiedObject(&parentObj, parentObjRef);
104
105 Object prevItemObject;
106 Object nextItemObject;
107
108 Ref outlineItemRef = xref->addIndirectObject(outlineItem);
109
110 // the next two statements fix up the parent object
111 // for clarity we separate this out
112 if (it == items.begin()) {
113 // we will be the first item in the list
114 // fix our parent
115 parentObj.dictSet("First", Object(outlineItemRef));
116 }
117 if (it == items.end()) {
118 // we will be the last item on the list
119 // fix up our parent
120 parentObj.dictSet("Last", Object(outlineItemRef));
121 }
122
123 if (it == items.end()) {
124 if (!items.empty()) {
125 // insert at the end, we handle this separately
126 prevItemObject = xref->fetch((*(it - 1))->getRef());
127 prevItemObject.dictSet("Next", Object(outlineItemRef));
128 outlineItem.dictSet("Prev", Object((*(it - 1))->getRef()));
129 xref->setModifiedObject(&prevItemObject, (*(it - 1))->getRef());
130 }
131 } else {
132 nextItemObject = xref->fetch((*it)->getRef());
133 nextItemObject.dictSet("Prev", Object(outlineItemRef));
134 xref->setModifiedObject(&nextItemObject, (*it)->getRef());
135
136 outlineItem.dictSet("Next", Object((*(it))->getRef()));
137
138 if (it != items.begin()) {
139 prevItemObject = xref->fetch((*(it - 1))->getRef());
140 prevItemObject.dictSet("Next", Object(outlineItemRef));
141 outlineItem.dictSet("Prev", Object((*(it - 1))->getRef()));
142 xref->setModifiedObject(&prevItemObject, (*(it - 1))->getRef());
143 }
144 }
145
146 OutlineItem *item = new OutlineItem(outlineItem.getDict(), outlineItemRef, nullptr, xref, doc);
147
148 items.insert(it, item);
149 }
150
insertChild(const std::string & itemTitle,int destPageNum,unsigned int pos)151 void Outline::insertChild(const std::string &itemTitle, int destPageNum, unsigned int pos)
152 {
153 Ref outlineObjRef = xref->getCatalog().dictLookupNF("Outlines").getRef();
154 insertChildHelper(itemTitle, destPageNum, pos, outlineObjRef, doc, xref, *items);
155 }
156
157 // ref is a valid reference to a list
158 // walk the list and free any children
159 // returns the number items deleted (just in case)
recursiveRemoveList(Ref ref,XRef * xref)160 static int recursiveRemoveList(Ref ref, XRef *xref)
161 {
162 int count = 0;
163 bool done = false;
164
165 Ref nextRef;
166 Object tempObj;
167
168 while (!done) {
169 tempObj = xref->fetch(ref);
170
171 if (!tempObj.isDict()) {
172 // something horrible has happened
173 break;
174 }
175
176 const Object &firstRef = tempObj.dictLookupNF("First");
177 if (firstRef.isRef()) {
178 count += recursiveRemoveList(firstRef.getRef(), xref);
179 }
180
181 const Object &nextObjRef = tempObj.dictLookupNF("Next");
182 if (nextObjRef.isRef()) {
183 nextRef = nextObjRef.getRef();
184 } else {
185 done = true;
186 }
187 xref->removeIndirectObject(ref);
188 count++;
189 ref = nextRef;
190 }
191 return count;
192 }
193
removeChildHelper(unsigned int pos,PDFDoc * doc,XRef * xref,std::vector<OutlineItem * > & items)194 static void removeChildHelper(unsigned int pos, PDFDoc *doc, XRef *xref, std::vector<OutlineItem *> &items)
195 {
196 std::vector<OutlineItem *>::const_iterator it;
197 if (pos >= items.size()) {
198 // position is out of range, do nothing
199 return;
200 } else {
201 it = items.begin() + pos;
202 }
203
204 // relink around this node
205 Object itemObject = xref->fetch((*it)->getRef());
206 Object parentObj = itemObject.dictLookup("Parent");
207 Object prevItemObject = itemObject.dictLookup("Prev");
208 Object nextItemObject = itemObject.dictLookup("Next");
209
210 // delete 1 from the parent Count if it's positive
211 Object countObj = parentObj.dictLookup("Count");
212 int count = countObj.getInt();
213 if (count > 0) {
214 count--;
215 parentObj.dictSet("Count", Object(count));
216 xref->setModifiedObject(&parentObj, itemObject.dictLookupNF("Parent").getRef());
217 }
218
219 if (!prevItemObject.isNull() && !nextItemObject.isNull()) {
220 // deletion is in the middle
221 prevItemObject.dictSet("Next", Object((*(it + 1))->getRef()));
222 xref->setModifiedObject(&prevItemObject, (*(it - 1))->getRef());
223
224 nextItemObject.dictSet("Prev", Object((*(it - 1))->getRef()));
225 xref->setModifiedObject(&nextItemObject, (*(it + 1))->getRef());
226 } else if (prevItemObject.isNull() && nextItemObject.isNull()) {
227 // deletion is only child
228 parentObj.dictRemove("First");
229 parentObj.dictRemove("Last");
230 xref->setModifiedObject(&parentObj, itemObject.dictLookupNF("Parent").getRef());
231 } else if (prevItemObject.isNull()) {
232 // deletion at the front
233 parentObj.dictSet("First", Object((*(it + 1))->getRef()));
234 xref->setModifiedObject(&parentObj, itemObject.dictLookupNF("Parent").getRef());
235
236 nextItemObject.dictRemove("Prev");
237 xref->setModifiedObject(&nextItemObject, (*(it + 1))->getRef());
238 } else {
239 // deletion at the end
240 parentObj.dictSet("Last", Object((*(it - 1))->getRef()));
241 xref->setModifiedObject(&parentObj, itemObject.dictLookupNF("Parent").getRef());
242 prevItemObject.dictRemove("Next");
243 xref->setModifiedObject(&prevItemObject, (*(it - 1))->getRef());
244 }
245
246 // free any children
247 const Object &firstRef = itemObject.dictLookupNF("First");
248 if (firstRef.isRef()) {
249 recursiveRemoveList(firstRef.getRef(), xref);
250 }
251
252 // free the pdf objects and the representation
253 xref->removeIndirectObject((*it)->getRef());
254 OutlineItem *oi = *it;
255 items.erase(it);
256 // deletion of the OutlineItem will delete all child
257 // outline items in its destructor
258 delete oi;
259 }
260
removeChild(unsigned int pos)261 void Outline::removeChild(unsigned int pos)
262 {
263 removeChildHelper(pos, doc, xref, *items);
264 }
265
266 //------------------------------------------------------------------------
267
addOutlineTreeNodeList(const std::vector<OutlineTreeNode> & nodeList,Ref & parentRef,Ref & firstRef,Ref & lastRef)268 int Outline::addOutlineTreeNodeList(const std::vector<OutlineTreeNode> &nodeList, Ref &parentRef, Ref &firstRef, Ref &lastRef)
269 {
270 firstRef = Ref::INVALID();
271 lastRef = Ref::INVALID();
272 if (nodeList.empty()) {
273 return 0;
274 }
275
276 int itemCount = 0;
277 Ref prevNodeRef = Ref::INVALID();
278
279 for (auto &node : nodeList) {
280
281 Array *a = new Array(doc->getXRef());
282 Ref *pageRef = doc->getCatalog()->getPageRef(node.destPageNum);
283 if (pageRef != nullptr) {
284 a->add(Object(*pageRef));
285 } else {
286 // if the page obj doesn't exist put the page number
287 // PDF32000-2008 12.3.2.2 Para 2
288 // as if it's a "Remote-Go-To Actions"
289 // it's not strictly valid, but most viewers seem
290 // to handle it without crashing
291 // alternately, could put 0, or omit it
292 a->add(Object(node.destPageNum - 1));
293 }
294 a->add(Object(objName, "Fit"));
295
296 Object outlineItem = Object(new Dict(doc->getXRef()));
297 Ref outlineItemRef = doc->getXRef()->addIndirectObject(outlineItem);
298
299 if (firstRef == Ref::INVALID()) {
300 firstRef = outlineItemRef;
301 }
302 lastRef = outlineItemRef;
303
304 GooString *g = new GooString(node.title);
305 outlineItem.dictSet("Title", Object(g));
306 outlineItem.dictSet("Dest", Object(a));
307 itemCount++;
308
309 if (prevNodeRef != Ref::INVALID()) {
310 outlineItem.dictSet("Prev", Object(prevNodeRef));
311
312 // maybe easier way to fix up the previous object
313 Object prevOutlineItem = xref->fetch(prevNodeRef);
314 prevOutlineItem.dictSet("Next", Object(outlineItemRef));
315 xref->setModifiedObject(&prevOutlineItem, prevNodeRef);
316 }
317 prevNodeRef = outlineItemRef;
318
319 Ref firstChildRef;
320 Ref lastChildRef;
321 itemCount += addOutlineTreeNodeList(node.children, outlineItemRef, firstChildRef, lastChildRef);
322
323 if (firstChildRef != Ref::INVALID()) {
324 outlineItem.dictSet("First", Object(firstChildRef));
325 outlineItem.dictSet("Last", Object(lastChildRef));
326 }
327 outlineItem.dictSet("Count", Object(itemCount));
328 outlineItem.dictAdd("Parent", Object(parentRef));
329 }
330 return itemCount;
331 }
332
333 /* insert an outline into a PDF
334 outline->setOutline({ {"page 1", 1,
335 { { "1.1", 1, {} } } },
336 {"page 2", 2, {} },
337 {"page 3", 3, {} },
338 {"page 4", 4,{ { "4.1", 4, {} },
339 { "4.2", 4, {} },
340 },
341 }
342 });
343 */
344
setOutline(const std::vector<OutlineTreeNode> & nodeList)345 void Outline::setOutline(const std::vector<OutlineTreeNode> &nodeList)
346 {
347 // check if outlineObj is an object, if it's not make sure it exists
348 if (!outlineObj->isDict()) {
349 outlineObj = doc->getCatalog()->getCreateOutline();
350
351 // make sure it was created
352 if (!outlineObj->isDict()) {
353 return;
354 }
355 }
356
357 Ref outlineObjRef = xref->getCatalog().dictLookupNF("Outlines").getRef();
358 Ref firstChildRef;
359 Ref lastChildRef;
360
361 // free any OutlineItem objects that will be replaced
362 const Object &firstChildRefObj = outlineObj->dictLookupNF("First");
363 if (firstChildRefObj.isRef()) {
364 recursiveRemoveList(firstChildRefObj.getRef(), xref);
365 }
366
367 const int count = addOutlineTreeNodeList(nodeList, outlineObjRef, firstChildRef, lastChildRef);
368
369 // modify the parent Outlines dict
370 if (firstChildRef != Ref::INVALID()) {
371 outlineObj->dictSet("First", Object(firstChildRef));
372 outlineObj->dictSet("Last", Object(lastChildRef));
373 } else {
374 // nothing was inserted into the outline, so just remove the
375 // child references in the top-level outline
376 outlineObj->dictRemove("First");
377 outlineObj->dictRemove("Last");
378 }
379 outlineObj->dictSet("Count", Object(count));
380 xref->setModifiedObject(outlineObj, outlineObjRef);
381
382 // reload the outline object from the xrefs
383
384 if (items) {
385 for (auto entry : *items) {
386 delete entry;
387 }
388 delete items;
389 }
390 const Object &first = outlineObj->dictLookupNF("First");
391 // we probably want to allow readItemList to create an empty list
392 // but for now just check and do it ourselves here
393 if (first.isRef()) {
394 items = OutlineItem::readItemList(nullptr, &first, xref, doc);
395 } else {
396 items = new std::vector<OutlineItem *>();
397 }
398 }
399
400 //------------------------------------------------------------------------
401
OutlineItem(const Dict * dict,Ref refA,OutlineItem * parentA,XRef * xrefA,PDFDoc * docA)402 OutlineItem::OutlineItem(const Dict *dict, Ref refA, OutlineItem *parentA, XRef *xrefA, PDFDoc *docA)
403 {
404 Object obj1;
405
406 ref = refA;
407 parent = parentA;
408 xref = xrefA;
409 doc = docA;
410 title = nullptr;
411 kids = nullptr;
412
413 obj1 = dict->lookup("Title");
414 if (obj1.isString()) {
415 const GooString *s = obj1.getString();
416 titleLen = TextStringToUCS4(s->toStr(), &title);
417 } else {
418 titleLen = 0;
419 }
420
421 obj1 = dict->lookup("Dest");
422 if (!obj1.isNull()) {
423 action = LinkAction::parseDest(&obj1);
424 } else {
425 obj1 = dict->lookup("A");
426 if (!obj1.isNull()) {
427 action = LinkAction::parseAction(&obj1);
428 }
429 }
430
431 startsOpen = false;
432 obj1 = dict->lookup("Count");
433 if (obj1.isInt()) {
434 if (obj1.getInt() > 0) {
435 startsOpen = true;
436 }
437 }
438 }
439
~OutlineItem()440 OutlineItem::~OutlineItem()
441 {
442 if (kids) {
443 for (auto entry : *kids) {
444 delete entry;
445 }
446 delete kids;
447 kids = nullptr;
448 }
449 if (title) {
450 gfree(title);
451 }
452 }
453
readItemList(OutlineItem * parent,const Object * firstItemRef,XRef * xrefA,PDFDoc * docA)454 std::vector<OutlineItem *> *OutlineItem::readItemList(OutlineItem *parent, const Object *firstItemRef, XRef *xrefA, PDFDoc *docA)
455 {
456 auto items = new std::vector<OutlineItem *>();
457
458 // could be a hash (unordered_map) too for better avg case check
459 // small number of objects expected, likely doesn't matter
460 std::set<Ref> alreadyRead;
461
462 OutlineItem *parentO = parent;
463 while (parentO) {
464 alreadyRead.insert(parentO->getRef());
465 parentO = parentO->parent;
466 }
467
468 Object tempObj = firstItemRef->copy();
469 while (tempObj.isRef() && (tempObj.getRefNum() >= 0) && (tempObj.getRefNum() < xrefA->getNumObjects()) && alreadyRead.find(tempObj.getRef()) == alreadyRead.end()) {
470 Object obj = tempObj.fetch(xrefA);
471 if (!obj.isDict()) {
472 break;
473 }
474 alreadyRead.insert(tempObj.getRef());
475 OutlineItem *item = new OutlineItem(obj.getDict(), tempObj.getRef(), parent, xrefA, docA);
476 items->push_back(item);
477 tempObj = obj.dictLookupNF("Next").copy();
478 }
479 return items;
480 }
481
open()482 void OutlineItem::open()
483 {
484 if (!kids) {
485 Object itemDict = xref->fetch(ref);
486 const Object &firstRef = itemDict.dictLookupNF("First");
487 kids = readItemList(this, &firstRef, xref, doc);
488 }
489 }
490
setTitle(const std::string & titleA)491 void OutlineItem::setTitle(const std::string &titleA)
492 {
493 gfree(title);
494
495 Object dict = xref->fetch(ref);
496 GooString *g = new GooString(titleA);
497 titleLen = TextStringToUCS4(g->toStr(), &title);
498 dict.dictSet("Title", Object(g));
499 xref->setModifiedObject(&dict, ref);
500 }
501
setPageDest(int i)502 bool OutlineItem::setPageDest(int i)
503 {
504 Object dict = xref->fetch(ref);
505 Object obj1;
506
507 if (i < 1) {
508 return false;
509 }
510
511 obj1 = dict.dictLookup("Dest");
512 if (!obj1.isNull()) {
513 int arrayLength = obj1.arrayGetLength();
514 for (int index = 0; index < arrayLength; index++) {
515 obj1.arrayRemove(0);
516 }
517 obj1.arrayAdd(Object(i - 1));
518 obj1.arrayAdd(Object(objName, "Fit"));
519
520 // unique_ptr will destroy previous on assignment
521 action = LinkAction::parseDest(&obj1);
522 } else {
523 obj1 = dict.dictLookup("A");
524 if (!obj1.isNull()) {
525 // RM 20210505 Implement
526 } else {
527 }
528 return false;
529 }
530
531 xref->setModifiedObject(&dict, ref);
532 return true;
533 }
534
insertChild(const std::string & itemTitle,int destPageNum,unsigned int pos)535 void OutlineItem::insertChild(const std::string &itemTitle, int destPageNum, unsigned int pos)
536 {
537 open();
538 insertChildHelper(itemTitle, destPageNum, pos, ref, doc, xref, *kids);
539 }
540
removeChild(unsigned int pos)541 void OutlineItem::removeChild(unsigned int pos)
542 {
543 open();
544 removeChildHelper(pos, doc, xref, *kids);
545 }
546
setStartsOpen(bool value)547 void OutlineItem::setStartsOpen(bool value)
548 {
549 startsOpen = value;
550 Object dict = xref->fetch(ref);
551 Object obj1 = dict.dictLookup("Count");
552 if (obj1.isInt()) {
553 const int count = obj1.getInt();
554 if ((count > 0 && !value) || (count < 0 && value)) {
555 // states requires change of sign
556 dict.dictSet("Count", Object(-count));
557 xref->setModifiedObject(&dict, ref);
558 }
559 }
560 }
561
hasKids()562 bool OutlineItem::hasKids()
563 {
564 open();
565 return !kids->empty();
566 }
567
getKids()568 const std::vector<OutlineItem *> *OutlineItem::getKids()
569 {
570 open();
571
572 if (!kids || kids->empty())
573 return nullptr;
574 else
575 return kids;
576 }
577