1 // Copyright 2016 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "core/fpdfdoc/cpdf_nametree.h"
8
9 #include <set>
10 #include <utility>
11 #include <vector>
12
13 #include "core/fpdfapi/parser/cpdf_array.h"
14 #include "core/fpdfapi/parser/cpdf_dictionary.h"
15 #include "core/fpdfapi/parser/cpdf_document.h"
16 #include "core/fpdfapi/parser/cpdf_reference.h"
17 #include "core/fpdfapi/parser/cpdf_string.h"
18 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
19 #include "core/fxcrt/check.h"
20 #include "core/fxcrt/ptr_util.h"
21 #include "core/fxcrt/stl_util.h"
22
23 namespace {
24
25 constexpr int kNameTreeMaxRecursion = 32;
26
GetNodeLimitsAndSanitize(CPDF_Array * pLimits)27 std::pair<WideString, WideString> GetNodeLimitsAndSanitize(
28 CPDF_Array* pLimits) {
29 DCHECK(pLimits);
30 WideString csLeft = pLimits->GetUnicodeTextAt(0);
31 WideString csRight = pLimits->GetUnicodeTextAt(1);
32 // If the lower limit is greater than the upper limit, swap them.
33 if (csLeft.Compare(csRight) > 0) {
34 pLimits->SetNewAt<CPDF_String>(0, csRight.AsStringView());
35 pLimits->SetNewAt<CPDF_String>(1, csLeft.AsStringView());
36 csLeft = pLimits->GetUnicodeTextAt(0);
37 csRight = pLimits->GetUnicodeTextAt(1);
38 }
39 while (pLimits->size() > 2)
40 pLimits->RemoveAt(pLimits->size() - 1);
41 return {csLeft, csRight};
42 }
43
44 // Get the limit arrays that leaf array |pFind| is under in the tree with root
45 // |pNode|. |pLimits| will hold all the limit arrays from the leaf up to before
46 // the root. Return true if successful.
GetNodeAncestorsLimitsInternal(const RetainPtr<CPDF_Dictionary> & pNode,const CPDF_Array * pFind,int nLevel,std::vector<CPDF_Array * > * pLimits)47 bool GetNodeAncestorsLimitsInternal(const RetainPtr<CPDF_Dictionary>& pNode,
48 const CPDF_Array* pFind,
49 int nLevel,
50 std::vector<CPDF_Array*>* pLimits) {
51 if (nLevel > kNameTreeMaxRecursion)
52 return false;
53
54 if (pNode->GetArrayFor("Names") == pFind) {
55 pLimits->push_back(pNode->GetMutableArrayFor("Limits").Get());
56 return true;
57 }
58
59 RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
60 if (!pKids)
61 return false;
62
63 for (size_t i = 0; i < pKids->size(); ++i) {
64 RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
65 if (!pKid)
66 continue;
67
68 if (GetNodeAncestorsLimitsInternal(pKid, pFind, nLevel + 1, pLimits)) {
69 pLimits->push_back(pNode->GetMutableArrayFor("Limits").Get());
70 return true;
71 }
72 }
73 return false;
74 }
75
76 // Wrapper for GetNodeAncestorsLimitsInternal() so callers do not need to know
77 // about the details.
GetNodeAncestorsLimits(const RetainPtr<CPDF_Dictionary> & pNode,const CPDF_Array * pFind)78 std::vector<CPDF_Array*> GetNodeAncestorsLimits(
79 const RetainPtr<CPDF_Dictionary>& pNode,
80 const CPDF_Array* pFind) {
81 std::vector<CPDF_Array*> results;
82 GetNodeAncestorsLimitsInternal(pNode, pFind, 0, &results);
83 return results;
84 }
85
86 // Upon the deletion of |csName| from leaf array |pFind|, update the ancestors
87 // of |pFind|. Specifically, the limits of |pFind|'s ancestors will be updated
88 // if needed, and any ancestors that are now empty will be removed.
UpdateNodesAndLimitsUponDeletion(CPDF_Dictionary * pNode,const CPDF_Array * pFind,const WideString & csName,int nLevel)89 bool UpdateNodesAndLimitsUponDeletion(CPDF_Dictionary* pNode,
90 const CPDF_Array* pFind,
91 const WideString& csName,
92 int nLevel) {
93 if (nLevel > kNameTreeMaxRecursion)
94 return false;
95
96 RetainPtr<CPDF_Array> pLimits = pNode->GetMutableArrayFor("Limits");
97 WideString csLeft;
98 WideString csRight;
99 if (pLimits)
100 std::tie(csLeft, csRight) = GetNodeLimitsAndSanitize(pLimits.Get());
101
102 RetainPtr<const CPDF_Array> pNames = pNode->GetArrayFor("Names");
103 if (pNames) {
104 if (pNames != pFind)
105 return false;
106 if (pNames->IsEmpty() || !pLimits)
107 return true;
108 if (csLeft != csName && csRight != csName)
109 return true;
110
111 // Since |csName| defines |pNode|'s limits, we need to loop through the
112 // names to find the new lower and upper limits.
113 WideString csNewLeft = csRight;
114 WideString csNewRight = csLeft;
115 for (size_t i = 0; i < pNames->size() / 2; ++i) {
116 WideString wsName = pNames->GetUnicodeTextAt(i * 2);
117 if (wsName.Compare(csNewLeft) < 0)
118 csNewLeft = wsName;
119 if (wsName.Compare(csNewRight) > 0)
120 csNewRight = wsName;
121 }
122 pLimits->SetNewAt<CPDF_String>(0, csNewLeft.AsStringView());
123 pLimits->SetNewAt<CPDF_String>(1, csNewRight.AsStringView());
124 return true;
125 }
126
127 RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
128 if (!pKids)
129 return false;
130
131 // Loop through the kids to find the leaf array |pFind|.
132 for (size_t i = 0; i < pKids->size(); ++i) {
133 RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
134 if (!pKid)
135 continue;
136 if (!UpdateNodesAndLimitsUponDeletion(pKid.Get(), pFind, csName,
137 nLevel + 1)) {
138 continue;
139 }
140 // Remove this child node if it's empty.
141 if ((pKid->KeyExist("Names") && pKid->GetArrayFor("Names")->IsEmpty()) ||
142 (pKid->KeyExist("Kids") && pKid->GetArrayFor("Kids")->IsEmpty())) {
143 pKids->RemoveAt(i);
144 }
145 if (pKids->IsEmpty() || !pLimits)
146 return true;
147 if (csLeft != csName && csRight != csName)
148 return true;
149
150 // Since |csName| defines |pNode|'s limits, we need to loop through the
151 // kids to find the new lower and upper limits.
152 WideString csNewLeft = csRight;
153 WideString csNewRight = csLeft;
154 for (size_t j = 0; j < pKids->size(); ++j) {
155 RetainPtr<const CPDF_Array> pKidLimits =
156 pKids->GetDictAt(j)->GetArrayFor("Limits");
157 DCHECK(pKidLimits);
158 if (pKidLimits->GetUnicodeTextAt(0).Compare(csNewLeft) < 0)
159 csNewLeft = pKidLimits->GetUnicodeTextAt(0);
160 if (pKidLimits->GetUnicodeTextAt(1).Compare(csNewRight) > 0)
161 csNewRight = pKidLimits->GetUnicodeTextAt(1);
162 }
163 pLimits->SetNewAt<CPDF_String>(0, csNewLeft.AsStringView());
164 pLimits->SetNewAt<CPDF_String>(1, csNewRight.AsStringView());
165 return true;
166 }
167 return false;
168 }
169
IsTraversedObject(const CPDF_Object * obj,std::set<uint32_t> * seen_obj_nums)170 bool IsTraversedObject(const CPDF_Object* obj,
171 std::set<uint32_t>* seen_obj_nums) {
172 uint32_t obj_num = obj->GetObjNum();
173 if (!obj_num)
174 return false;
175
176 bool inserted = seen_obj_nums->insert(obj_num).second;
177 return !inserted;
178 }
179
IsArrayWithTraversedObject(const CPDF_Array * array,std::set<uint32_t> * seen_obj_nums)180 bool IsArrayWithTraversedObject(const CPDF_Array* array,
181 std::set<uint32_t>* seen_obj_nums) {
182 if (IsTraversedObject(array, seen_obj_nums))
183 return true;
184
185 CPDF_ArrayLocker locker(array);
186 for (const auto& item : locker) {
187 if (IsTraversedObject(item.Get(), seen_obj_nums))
188 return true;
189 }
190 return false;
191 }
192
193 // Search for |csName| in the tree with root |pNode|. If successful, return the
194 // value that |csName| points to; |nIndex| will be the index of |csName|,
195 // |ppFind| will be the leaf array that |csName| is found in, and |pFindIndex|
196 // will be the index of |csName| in |ppFind|. If |csName| is not found, |ppFind|
197 // will be the leaf array that |csName| should be added to, and |pFindIndex|
198 // will be the index that it should be added at.
SearchNameNodeByNameInternal(const RetainPtr<CPDF_Dictionary> & pNode,const WideString & csName,int nLevel,size_t * nIndex,RetainPtr<CPDF_Array> * ppFind,int * pFindIndex,std::set<uint32_t> * seen_obj_nums)199 RetainPtr<const CPDF_Object> SearchNameNodeByNameInternal(
200 const RetainPtr<CPDF_Dictionary>& pNode,
201 const WideString& csName,
202 int nLevel,
203 size_t* nIndex,
204 RetainPtr<CPDF_Array>* ppFind,
205 int* pFindIndex,
206 std::set<uint32_t>* seen_obj_nums) {
207 if (nLevel > kNameTreeMaxRecursion)
208 return nullptr;
209
210 RetainPtr<CPDF_Array> pLimits = pNode->GetMutableArrayFor("Limits");
211 RetainPtr<CPDF_Array> pNames = pNode->GetMutableArrayFor("Names");
212 if (pNames && IsArrayWithTraversedObject(pNames.Get(), seen_obj_nums))
213 pNames.Reset();
214 if (pLimits && IsArrayWithTraversedObject(pLimits.Get(), seen_obj_nums))
215 pLimits.Reset();
216
217 if (pLimits) {
218 auto [csLeft, csRight] = GetNodeLimitsAndSanitize(pLimits.Get());
219 // Skip this node if the name to look for is smaller than its lower limit.
220 if (csName.Compare(csLeft) < 0)
221 return nullptr;
222
223 // Skip this node if the name to look for is greater than its higher limit,
224 // and the node itself is a leaf node.
225 if (csName.Compare(csRight) > 0 && pNames) {
226 if (ppFind)
227 *ppFind = pNames;
228 if (pFindIndex)
229 *pFindIndex = fxcrt::CollectionSize<int32_t>(*pNames) / 2 - 1;
230 return nullptr;
231 }
232 }
233
234 // If the node is a leaf node, look for the name in its names array.
235 if (pNames) {
236 size_t dwCount = pNames->size() / 2;
237 for (size_t i = 0; i < dwCount; i++) {
238 WideString csValue = pNames->GetUnicodeTextAt(i * 2);
239 int32_t iCompare = csValue.Compare(csName);
240 if (iCompare > 0)
241 break;
242 if (ppFind)
243 *ppFind = pNames;
244 if (pFindIndex)
245 *pFindIndex = pdfium::checked_cast<int32_t>(i);
246 if (iCompare < 0)
247 continue;
248
249 *nIndex += i;
250 return pNames->GetDirectObjectAt(i * 2 + 1);
251 }
252 *nIndex += dwCount;
253 return nullptr;
254 }
255
256 // Search through the node's children.
257 RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
258 if (!pKids || IsTraversedObject(pKids.Get(), seen_obj_nums))
259 return nullptr;
260
261 for (size_t i = 0; i < pKids->size(); i++) {
262 RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
263 if (!pKid || IsTraversedObject(pKid.Get(), seen_obj_nums))
264 continue;
265
266 RetainPtr<const CPDF_Object> pFound = SearchNameNodeByNameInternal(
267 pKid, csName, nLevel + 1, nIndex, ppFind, pFindIndex, seen_obj_nums);
268 if (pFound)
269 return pFound;
270 }
271 return nullptr;
272 }
273
274 // Wrapper for SearchNameNodeByNameInternal() so callers do not need to know
275 // about the details.
SearchNameNodeByName(const RetainPtr<CPDF_Dictionary> & pNode,const WideString & csName,RetainPtr<CPDF_Array> * ppFind,int * pFindIndex)276 RetainPtr<const CPDF_Object> SearchNameNodeByName(
277 const RetainPtr<CPDF_Dictionary>& pNode,
278 const WideString& csName,
279 RetainPtr<CPDF_Array>* ppFind,
280 int* pFindIndex) {
281 size_t nIndex = 0;
282 std::set<uint32_t> seen_obj_nums;
283 return SearchNameNodeByNameInternal(pNode, csName, 0, &nIndex, ppFind,
284 pFindIndex, &seen_obj_nums);
285 }
286
287 struct IndexSearchResult {
288 // For the n-th object in a tree, the key and value.
289 WideString key;
290 RetainPtr<CPDF_Object> value;
291 // The leaf node that holds `key` and `value`.
292 RetainPtr<CPDF_Array> container;
293 // The index for `key` in `container`. Must be even.
294 size_t index;
295 };
296
297 // Find the `nTargetPairIndex` node in the tree with root `pNode`. `nLevel`
298 // tracks the recursion level and `nCurPairIndex` tracks the progress towards
299 // `nTargetPairIndex`.
SearchNameNodeByIndexInternal(CPDF_Dictionary * pNode,size_t nTargetPairIndex,int nLevel,size_t * nCurPairIndex)300 std::optional<IndexSearchResult> SearchNameNodeByIndexInternal(
301 CPDF_Dictionary* pNode,
302 size_t nTargetPairIndex,
303 int nLevel,
304 size_t* nCurPairIndex) {
305 if (nLevel > kNameTreeMaxRecursion)
306 return std::nullopt;
307
308 RetainPtr<CPDF_Array> pNames = pNode->GetMutableArrayFor("Names");
309 if (pNames) {
310 size_t nCount = pNames->size() / 2;
311 if (nTargetPairIndex >= *nCurPairIndex + nCount) {
312 *nCurPairIndex += nCount;
313 return std::nullopt;
314 }
315
316 size_t index = 2 * (nTargetPairIndex - *nCurPairIndex);
317 RetainPtr<CPDF_Object> value = pNames->GetMutableDirectObjectAt(index + 1);
318 if (!value)
319 return std::nullopt;
320
321 IndexSearchResult result;
322 result.key = pNames->GetUnicodeTextAt(index);
323 result.value = std::move(value);
324 result.container = std::move(pNames);
325 result.index = index;
326 return result;
327 }
328
329 RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
330 if (!pKids)
331 return std::nullopt;
332
333 for (size_t i = 0; i < pKids->size(); i++) {
334 RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
335 if (!pKid)
336 continue;
337 std::optional<IndexSearchResult> result = SearchNameNodeByIndexInternal(
338 pKid.Get(), nTargetPairIndex, nLevel + 1, nCurPairIndex);
339 if (result.has_value())
340 return result;
341 }
342 return std::nullopt;
343 }
344
345 // Wrapper for SearchNameNodeByIndexInternal() so callers do not need to know
346 // about the details.
SearchNameNodeByIndex(CPDF_Dictionary * pNode,size_t nTargetPairIndex)347 std::optional<IndexSearchResult> SearchNameNodeByIndex(
348 CPDF_Dictionary* pNode,
349 size_t nTargetPairIndex) {
350 size_t nCurPairIndex = 0;
351 return SearchNameNodeByIndexInternal(pNode, nTargetPairIndex, 0,
352 &nCurPairIndex);
353 }
354
355 // Get the total number of key-value pairs in the tree with root |pNode|.
CountNamesInternal(const CPDF_Dictionary * pNode,int nLevel,std::set<const CPDF_Dictionary * > & seen)356 size_t CountNamesInternal(const CPDF_Dictionary* pNode,
357 int nLevel,
358 std::set<const CPDF_Dictionary*>& seen) {
359 if (nLevel > kNameTreeMaxRecursion)
360 return 0;
361
362 const bool inserted = seen.insert(pNode).second;
363 if (!inserted)
364 return 0;
365
366 RetainPtr<const CPDF_Array> pNames = pNode->GetArrayFor("Names");
367 if (pNames)
368 return pNames->size() / 2;
369
370 RetainPtr<const CPDF_Array> pKids = pNode->GetArrayFor("Kids");
371 if (!pKids)
372 return 0;
373
374 size_t nCount = 0;
375 for (size_t i = 0; i < pKids->size(); i++) {
376 RetainPtr<const CPDF_Dictionary> pKid = pKids->GetDictAt(i);
377 if (!pKid)
378 continue;
379
380 nCount += CountNamesInternal(pKid.Get(), nLevel + 1, seen);
381 }
382 return nCount;
383 }
384
GetNamedDestFromObject(RetainPtr<const CPDF_Object> obj)385 RetainPtr<const CPDF_Array> GetNamedDestFromObject(
386 RetainPtr<const CPDF_Object> obj) {
387 RetainPtr<const CPDF_Array> array = ToArray(obj);
388 if (array)
389 return array;
390 RetainPtr<const CPDF_Dictionary> dict = ToDictionary(obj);
391 if (dict)
392 return dict->GetArrayFor("D");
393 return nullptr;
394 }
395
LookupOldStyleNamedDest(CPDF_Document * pDoc,const ByteString & name)396 RetainPtr<const CPDF_Array> LookupOldStyleNamedDest(CPDF_Document* pDoc,
397 const ByteString& name) {
398 RetainPtr<const CPDF_Dictionary> pDests =
399 pDoc->GetRoot()->GetDictFor("Dests");
400 if (!pDests)
401 return nullptr;
402 return GetNamedDestFromObject(pDests->GetDirectObjectFor(name));
403 }
404
405 } // namespace
406
CPDF_NameTree(RetainPtr<CPDF_Dictionary> pRoot)407 CPDF_NameTree::CPDF_NameTree(RetainPtr<CPDF_Dictionary> pRoot)
408 : m_pRoot(std::move(pRoot)) {
409 DCHECK(m_pRoot);
410 }
411
412 CPDF_NameTree::~CPDF_NameTree() = default;
413
414 // static
Create(CPDF_Document * pDoc,const ByteString & category)415 std::unique_ptr<CPDF_NameTree> CPDF_NameTree::Create(
416 CPDF_Document* pDoc,
417 const ByteString& category) {
418 RetainPtr<CPDF_Dictionary> pRoot = pDoc->GetMutableRoot();
419 if (!pRoot)
420 return nullptr;
421
422 RetainPtr<CPDF_Dictionary> pNames = pRoot->GetMutableDictFor("Names");
423 if (!pNames)
424 return nullptr;
425
426 RetainPtr<CPDF_Dictionary> pCategory = pNames->GetMutableDictFor(category);
427 if (!pCategory)
428 return nullptr;
429
430 return pdfium::WrapUnique(
431 new CPDF_NameTree(std::move(pCategory))); // Private ctor.
432 }
433
434 // static
CreateWithRootNameArray(CPDF_Document * pDoc,const ByteString & category)435 std::unique_ptr<CPDF_NameTree> CPDF_NameTree::CreateWithRootNameArray(
436 CPDF_Document* pDoc,
437 const ByteString& category) {
438 RetainPtr<CPDF_Dictionary> pRoot = pDoc->GetMutableRoot();
439 if (!pRoot)
440 return nullptr;
441
442 // Retrieve the document's Names dictionary; create it if missing.
443 RetainPtr<CPDF_Dictionary> pNames = pRoot->GetMutableDictFor("Names");
444 if (!pNames) {
445 pNames = pDoc->NewIndirect<CPDF_Dictionary>();
446 pRoot->SetNewFor<CPDF_Reference>("Names", pDoc, pNames->GetObjNum());
447 }
448
449 // Create the |category| dictionary if missing.
450 RetainPtr<CPDF_Dictionary> pCategory = pNames->GetMutableDictFor(category);
451 if (!pCategory) {
452 pCategory = pDoc->NewIndirect<CPDF_Dictionary>();
453 pCategory->SetNewFor<CPDF_Array>("Names");
454 pNames->SetNewFor<CPDF_Reference>(category, pDoc, pCategory->GetObjNum());
455 }
456
457 return pdfium::WrapUnique(new CPDF_NameTree(pCategory)); // Private ctor.
458 }
459
460 // static
CreateForTesting(CPDF_Dictionary * pRoot)461 std::unique_ptr<CPDF_NameTree> CPDF_NameTree::CreateForTesting(
462 CPDF_Dictionary* pRoot) {
463 return pdfium::WrapUnique(
464 new CPDF_NameTree(pdfium::WrapRetain(pRoot))); // Private ctor.
465 }
466
467 // static
LookupNamedDest(CPDF_Document * pDoc,const ByteString & name)468 RetainPtr<const CPDF_Array> CPDF_NameTree::LookupNamedDest(
469 CPDF_Document* pDoc,
470 const ByteString& name) {
471 RetainPtr<const CPDF_Array> dest_array;
472 std::unique_ptr<CPDF_NameTree> name_tree = Create(pDoc, "Dests");
473 if (name_tree)
474 dest_array = name_tree->LookupNewStyleNamedDest(name);
475 if (!dest_array)
476 dest_array = LookupOldStyleNamedDest(pDoc, name);
477 return dest_array;
478 }
479
GetCount() const480 size_t CPDF_NameTree::GetCount() const {
481 std::set<const CPDF_Dictionary*> seen;
482 return CountNamesInternal(m_pRoot.Get(), 0, seen);
483 }
484
AddValueAndName(RetainPtr<CPDF_Object> pObj,const WideString & name)485 bool CPDF_NameTree::AddValueAndName(RetainPtr<CPDF_Object> pObj,
486 const WideString& name) {
487 RetainPtr<CPDF_Array> pFind;
488 int nFindIndex = -1;
489 // Handle the corner case where the root node is empty. i.e. No kids and no
490 // names. In which case, just insert into it and skip all the searches.
491 RetainPtr<CPDF_Array> pNames = m_pRoot->GetMutableArrayFor("Names");
492 if (pNames && pNames->IsEmpty() && !m_pRoot->GetArrayFor("Kids"))
493 pFind = pNames;
494
495 if (!pFind) {
496 // Fail if the tree already contains this name or if the tree is too deep.
497 if (SearchNameNodeByName(m_pRoot, name, &pFind, &nFindIndex))
498 return false;
499 }
500
501 // If the returned |pFind| is a nullptr, then |name| is smaller than all
502 // existing entries in the tree, and we did not find a leaf array to place
503 // |name| into. We instead will find the leftmost leaf array in which to place
504 // |name| and |pObj|.
505 if (!pFind) {
506 std::optional<IndexSearchResult> result =
507 SearchNameNodeByIndex(m_pRoot.Get(), 0);
508 if (!result.has_value()) {
509 // Give up if that fails too.
510 return false;
511 }
512
513 pFind = result.value().container;
514 DCHECK(pFind);
515 }
516
517 // Insert the name and the object into the leaf array found. Note that the
518 // insertion position is right after the key-value pair returned by |index|.
519 size_t nNameIndex = (nFindIndex + 1) * 2;
520 size_t nValueIndex = nNameIndex + 1;
521 pFind->InsertNewAt<CPDF_String>(nNameIndex, name.AsStringView());
522 pFind->InsertAt(nValueIndex, std::move(pObj));
523
524 // Expand the limits that the newly added name is under, if the name falls
525 // outside of the limits of its leaf array or any arrays above it.
526 std::vector<CPDF_Array*> all_limits =
527 GetNodeAncestorsLimits(m_pRoot, pFind.Get());
528 for (auto* pLimits : all_limits) {
529 if (!pLimits)
530 continue;
531
532 if (name.Compare(pLimits->GetUnicodeTextAt(0)) < 0)
533 pLimits->SetNewAt<CPDF_String>(0, name.AsStringView());
534
535 if (name.Compare(pLimits->GetUnicodeTextAt(1)) > 0)
536 pLimits->SetNewAt<CPDF_String>(1, name.AsStringView());
537 }
538 return true;
539 }
540
DeleteValueAndName(size_t nIndex)541 bool CPDF_NameTree::DeleteValueAndName(size_t nIndex) {
542 std::optional<IndexSearchResult> result =
543 SearchNameNodeByIndex(m_pRoot.Get(), nIndex);
544 if (!result) {
545 // Fail if the tree does not contain |nIndex|.
546 return false;
547 }
548
549 // Remove the name and the object from the leaf array |pFind|.
550 RetainPtr<CPDF_Array> pFind = result.value().container;
551 pFind->RemoveAt(result.value().index + 1);
552 pFind->RemoveAt(result.value().index);
553
554 // Delete empty nodes and update the limits of |pFind|'s ancestors as needed.
555 UpdateNodesAndLimitsUponDeletion(m_pRoot.Get(), pFind.Get(),
556 result.value().key, 0);
557 return true;
558 }
559
LookupValueAndName(size_t nIndex,WideString * csName) const560 RetainPtr<CPDF_Object> CPDF_NameTree::LookupValueAndName(
561 size_t nIndex,
562 WideString* csName) const {
563 std::optional<IndexSearchResult> result =
564 SearchNameNodeByIndex(m_pRoot.Get(), nIndex);
565 if (!result) {
566 csName->clear();
567 return nullptr;
568 }
569
570 *csName = std::move(result.value().key);
571 return result.value().value;
572 }
573
LookupValue(const WideString & csName) const574 RetainPtr<const CPDF_Object> CPDF_NameTree::LookupValue(
575 const WideString& csName) const {
576 return SearchNameNodeByName(m_pRoot, csName, nullptr, nullptr);
577 }
578
LookupNewStyleNamedDest(const ByteString & sName)579 RetainPtr<const CPDF_Array> CPDF_NameTree::LookupNewStyleNamedDest(
580 const ByteString& sName) {
581 return GetNamedDestFromObject(
582 LookupValue(PDF_DecodeText(sName.unsigned_span())));
583 }
584