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