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