• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 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/fpdfapi/parser/cpdf_document.h"
8 
9 #include <utility>
10 
11 #include "core/fpdfapi/parser/cpdf_array.h"
12 #include "core/fpdfapi/parser/cpdf_dictionary.h"
13 #include "core/fpdfapi/parser/cpdf_linearized_header.h"
14 #include "core/fpdfapi/parser/cpdf_name.h"
15 #include "core/fpdfapi/parser/cpdf_number.h"
16 #include "core/fpdfapi/parser/cpdf_parser.h"
17 #include "core/fpdfapi/parser/cpdf_read_validator.h"
18 #include "core/fpdfapi/parser/cpdf_reference.h"
19 #include "core/fpdfapi/parser/cpdf_stream.h"
20 #include "core/fpdfapi/parser/cpdf_stream_acc.h"
21 #include "core/fpdfapi/parser/fpdf_parser_utility.h"
22 #include "core/fxcodec/jbig2/JBig2_DocumentContext.h"
23 #include "core/fxcrt/fx_codepage.h"
24 #include "core/fxcrt/scoped_set_insertion.h"
25 #include "core/fxcrt/stl_util.h"
26 #include "third_party/abseil-cpp/absl/types/optional.h"
27 #include "third_party/base/check.h"
28 #include "third_party/base/containers/contains.h"
29 
30 namespace {
31 
32 const int kMaxPageLevel = 1024;
33 
34 // Returns a value in the range [0, `CPDF_Document::kPageMaxNum`), or nullopt on
35 // error.
CountPages(RetainPtr<CPDF_Dictionary> pPages,std::set<RetainPtr<CPDF_Dictionary>> * visited_pages)36 absl::optional<int> CountPages(
37     RetainPtr<CPDF_Dictionary> pPages,
38     std::set<RetainPtr<CPDF_Dictionary>>* visited_pages) {
39   int count = pPages->GetIntegerFor("Count");
40   if (count > 0 && count < CPDF_Document::kPageMaxNum)
41     return count;
42   RetainPtr<CPDF_Array> pKidList = pPages->GetMutableArrayFor("Kids");
43   if (!pKidList)
44     return 0;
45   count = 0;
46   for (size_t i = 0; i < pKidList->size(); i++) {
47     RetainPtr<CPDF_Dictionary> pKid = pKidList->GetMutableDictAt(i);
48     if (!pKid || pdfium::Contains(*visited_pages, pKid))
49       continue;
50     if (pKid->KeyExist("Kids")) {
51       // Use |visited_pages| to help detect circular references of pages.
52       ScopedSetInsertion<RetainPtr<CPDF_Dictionary>> local_add(visited_pages,
53                                                                pKid);
54       absl::optional<int> local_count =
55           CountPages(std::move(pKid), visited_pages);
56       if (!local_count.has_value()) {
57         return absl::nullopt;  // Propagate error.
58       }
59       count += local_count.value();
60     } else {
61       // This page is a leaf node.
62       count++;
63     }
64     if (count >= CPDF_Document::kPageMaxNum) {
65       return absl::nullopt;  // Error: too many pages.
66     }
67   }
68   pPages->SetNewFor<CPDF_Number>("Count", count);
69   return count;
70 }
71 
FindPageIndex(const CPDF_Dictionary * pNode,uint32_t * skip_count,uint32_t objnum,int * index,int level)72 int FindPageIndex(const CPDF_Dictionary* pNode,
73                   uint32_t* skip_count,
74                   uint32_t objnum,
75                   int* index,
76                   int level) {
77   if (!pNode->KeyExist("Kids")) {
78     if (objnum == pNode->GetObjNum())
79       return *index;
80 
81     if (*skip_count != 0)
82       (*skip_count)--;
83 
84     (*index)++;
85     return -1;
86   }
87 
88   RetainPtr<const CPDF_Array> pKidList = pNode->GetArrayFor("Kids");
89   if (!pKidList)
90     return -1;
91 
92   if (level >= kMaxPageLevel)
93     return -1;
94 
95   size_t count = pNode->GetIntegerFor("Count");
96   if (count <= *skip_count) {
97     (*skip_count) -= count;
98     (*index) += count;
99     return -1;
100   }
101 
102   if (count && count == pKidList->size()) {
103     for (size_t i = 0; i < count; i++) {
104       RetainPtr<const CPDF_Reference> pKid =
105           ToReference(pKidList->GetObjectAt(i));
106       if (pKid && pKid->GetRefObjNum() == objnum)
107         return static_cast<int>(*index + i);
108     }
109   }
110 
111   for (size_t i = 0; i < pKidList->size(); i++) {
112     RetainPtr<const CPDF_Dictionary> pKid = pKidList->GetDictAt(i);
113     if (!pKid || pKid == pNode)
114       continue;
115 
116     int found_index =
117         FindPageIndex(pKid.Get(), skip_count, objnum, index, level + 1);
118     if (found_index >= 0)
119       return found_index;
120   }
121   return -1;
122 }
123 
124 }  // namespace
125 
CPDF_Document(std::unique_ptr<RenderDataIface> pRenderData,std::unique_ptr<PageDataIface> pPageData)126 CPDF_Document::CPDF_Document(std::unique_ptr<RenderDataIface> pRenderData,
127                              std::unique_ptr<PageDataIface> pPageData)
128     : m_pDocRender(std::move(pRenderData)),
129       m_pDocPage(std::move(pPageData)),
130       m_StockFontClearer(m_pDocPage.get()) {
131   m_pDocRender->SetDocument(this);
132   m_pDocPage->SetDocument(this);
133 }
134 
~CPDF_Document()135 CPDF_Document::~CPDF_Document() {
136   // Be absolutely certain that |m_pExtension| is null before destroying
137   // the extension, to avoid re-entering it while being destroyed. clang
138   // seems to already do this for us, but the C++ standards seem to
139   // indicate the opposite.
140   m_pExtension.reset();
141 }
142 
143 // static
IsValidPageObject(const CPDF_Object * obj)144 bool CPDF_Document::IsValidPageObject(const CPDF_Object* obj) {
145   // See ISO 32000-1:2008 spec, table 30.
146   return ValidateDictType(ToDictionary(obj), "Page");
147 }
148 
ParseIndirectObject(uint32_t objnum)149 RetainPtr<CPDF_Object> CPDF_Document::ParseIndirectObject(uint32_t objnum) {
150   return m_pParser ? m_pParser->ParseIndirectObject(objnum) : nullptr;
151 }
152 
TryInit()153 bool CPDF_Document::TryInit() {
154   SetLastObjNum(m_pParser->GetLastObjNum());
155 
156   RetainPtr<CPDF_Object> pRootObj =
157       GetOrParseIndirectObject(m_pParser->GetRootObjNum());
158   if (pRootObj)
159     m_pRootDict = pRootObj->GetMutableDict();
160 
161   LoadPages();
162   return GetRoot() && GetPageCount() > 0;
163 }
164 
LoadDoc(RetainPtr<IFX_SeekableReadStream> pFileAccess,const ByteString & password)165 CPDF_Parser::Error CPDF_Document::LoadDoc(
166     RetainPtr<IFX_SeekableReadStream> pFileAccess,
167     const ByteString& password) {
168   if (!m_pParser)
169     SetParser(std::make_unique<CPDF_Parser>(this));
170 
171   return HandleLoadResult(
172       m_pParser->StartParse(std::move(pFileAccess), password));
173 }
174 
LoadLinearizedDoc(RetainPtr<CPDF_ReadValidator> validator,const ByteString & password)175 CPDF_Parser::Error CPDF_Document::LoadLinearizedDoc(
176     RetainPtr<CPDF_ReadValidator> validator,
177     const ByteString& password) {
178   if (!m_pParser)
179     SetParser(std::make_unique<CPDF_Parser>(this));
180 
181   return HandleLoadResult(
182       m_pParser->StartLinearizedParse(std::move(validator), password));
183 }
184 
LoadPages()185 void CPDF_Document::LoadPages() {
186   const CPDF_LinearizedHeader* linearized_header =
187       m_pParser->GetLinearizedHeader();
188   if (!linearized_header) {
189     m_PageList.resize(RetrievePageCount());
190     return;
191   }
192 
193   uint32_t objnum = linearized_header->GetFirstPageObjNum();
194   if (!IsValidPageObject(GetOrParseIndirectObject(objnum).Get())) {
195     m_PageList.resize(RetrievePageCount());
196     return;
197   }
198 
199   uint32_t first_page_num = linearized_header->GetFirstPageNo();
200   uint32_t page_count = linearized_header->GetPageCount();
201   DCHECK(first_page_num < page_count);
202   m_PageList.resize(page_count);
203   m_PageList[first_page_num] = objnum;
204 }
205 
TraversePDFPages(int iPage,int * nPagesToGo,size_t level)206 RetainPtr<CPDF_Dictionary> CPDF_Document::TraversePDFPages(int iPage,
207                                                            int* nPagesToGo,
208                                                            size_t level) {
209   if (*nPagesToGo < 0 || m_bReachedMaxPageLevel)
210     return nullptr;
211 
212   RetainPtr<CPDF_Dictionary> pPages = m_pTreeTraversal[level].first;
213   RetainPtr<CPDF_Array> pKidList = pPages->GetMutableArrayFor("Kids");
214   if (!pKidList) {
215     m_pTreeTraversal.pop_back();
216     if (*nPagesToGo != 1)
217       return nullptr;
218     m_PageList[iPage] = pPages->GetObjNum();
219     return pPages;
220   }
221   if (level >= kMaxPageLevel) {
222     m_pTreeTraversal.pop_back();
223     m_bReachedMaxPageLevel = true;
224     return nullptr;
225   }
226   RetainPtr<CPDF_Dictionary> page;
227   for (size_t i = m_pTreeTraversal[level].second; i < pKidList->size(); i++) {
228     if (*nPagesToGo == 0)
229       break;
230     pKidList->ConvertToIndirectObjectAt(i, this);
231     RetainPtr<CPDF_Dictionary> pKid = pKidList->GetMutableDictAt(i);
232     if (!pKid) {
233       (*nPagesToGo)--;
234       m_pTreeTraversal[level].second++;
235       continue;
236     }
237     if (pKid == pPages) {
238       m_pTreeTraversal[level].second++;
239       continue;
240     }
241     if (!pKid->KeyExist("Kids")) {
242       m_PageList[iPage - (*nPagesToGo) + 1] = pKid->GetObjNum();
243       (*nPagesToGo)--;
244       m_pTreeTraversal[level].second++;
245       if (*nPagesToGo == 0) {
246         page = std::move(pKid);
247         break;
248       }
249     } else {
250       // If the vector has size level+1, the child is not in yet
251       if (m_pTreeTraversal.size() == level + 1)
252         m_pTreeTraversal.emplace_back(std::move(pKid), 0);
253       // Now m_pTreeTraversal[level+1] should exist and be equal to pKid.
254       RetainPtr<CPDF_Dictionary> pPageKid =
255           TraversePDFPages(iPage, nPagesToGo, level + 1);
256       // Check if child was completely processed, i.e. it popped itself out
257       if (m_pTreeTraversal.size() == level + 1)
258         m_pTreeTraversal[level].second++;
259       // If child did not finish, no pages to go, or max level reached, end
260       if (m_pTreeTraversal.size() != level + 1 || *nPagesToGo == 0 ||
261           m_bReachedMaxPageLevel) {
262         page = std::move(pPageKid);
263         break;
264       }
265     }
266   }
267   if (m_pTreeTraversal[level].second == pKidList->size())
268     m_pTreeTraversal.pop_back();
269   return page;
270 }
271 
ResetTraversal()272 void CPDF_Document::ResetTraversal() {
273   m_iNextPageToTraverse = 0;
274   m_bReachedMaxPageLevel = false;
275   m_pTreeTraversal.clear();
276 }
277 
SetParser(std::unique_ptr<CPDF_Parser> pParser)278 void CPDF_Document::SetParser(std::unique_ptr<CPDF_Parser> pParser) {
279   DCHECK(!m_pParser);
280   m_pParser = std::move(pParser);
281 }
282 
HandleLoadResult(CPDF_Parser::Error error)283 CPDF_Parser::Error CPDF_Document::HandleLoadResult(CPDF_Parser::Error error) {
284   if (error == CPDF_Parser::SUCCESS)
285     m_bHasValidCrossReferenceTable = !m_pParser->xref_table_rebuilt();
286   return error;
287 }
288 
GetPagesDict() const289 RetainPtr<const CPDF_Dictionary> CPDF_Document::GetPagesDict() const {
290   const CPDF_Dictionary* pRoot = GetRoot();
291   return pRoot ? pRoot->GetDictFor("Pages") : nullptr;
292 }
293 
GetMutablePagesDict()294 RetainPtr<CPDF_Dictionary> CPDF_Document::GetMutablePagesDict() {
295   return pdfium::WrapRetain(
296       const_cast<CPDF_Dictionary*>(this->GetPagesDict().Get()));
297 }
298 
IsPageLoaded(int iPage) const299 bool CPDF_Document::IsPageLoaded(int iPage) const {
300   return !!m_PageList[iPage];
301 }
302 
GetPageDictionary(int iPage)303 RetainPtr<const CPDF_Dictionary> CPDF_Document::GetPageDictionary(int iPage) {
304   if (!fxcrt::IndexInBounds(m_PageList, iPage))
305     return nullptr;
306 
307   const uint32_t objnum = m_PageList[iPage];
308   if (objnum) {
309     RetainPtr<CPDF_Dictionary> result =
310         ToDictionary(GetOrParseIndirectObject(objnum));
311     if (result)
312       return result;
313   }
314 
315   RetainPtr<CPDF_Dictionary> pPages = GetMutablePagesDict();
316   if (!pPages)
317     return nullptr;
318 
319   if (m_pTreeTraversal.empty()) {
320     ResetTraversal();
321     m_pTreeTraversal.emplace_back(std::move(pPages), 0);
322   }
323   int nPagesToGo = iPage - m_iNextPageToTraverse + 1;
324   RetainPtr<CPDF_Dictionary> pPage = TraversePDFPages(iPage, &nPagesToGo, 0);
325   m_iNextPageToTraverse = iPage + 1;
326   return pPage;
327 }
328 
GetMutablePageDictionary(int iPage)329 RetainPtr<CPDF_Dictionary> CPDF_Document::GetMutablePageDictionary(int iPage) {
330   return pdfium::WrapRetain(
331       const_cast<CPDF_Dictionary*>(GetPageDictionary(iPage).Get()));
332 }
333 
SetPageObjNum(int iPage,uint32_t objNum)334 void CPDF_Document::SetPageObjNum(int iPage, uint32_t objNum) {
335   m_PageList[iPage] = objNum;
336 }
337 
GetOrCreateCodecContext()338 JBig2_DocumentContext* CPDF_Document::GetOrCreateCodecContext() {
339   if (!m_pCodecContext)
340     m_pCodecContext = std::make_unique<JBig2_DocumentContext>();
341   return m_pCodecContext.get();
342 }
343 
CreateModifiedAPStream()344 RetainPtr<CPDF_Stream> CPDF_Document::CreateModifiedAPStream() {
345   auto stream = NewIndirect<CPDF_Stream>();
346   m_ModifiedAPStreamIDs.insert(stream->GetObjNum());
347   return stream;
348 }
349 
IsModifiedAPStream(const CPDF_Stream * stream) const350 bool CPDF_Document::IsModifiedAPStream(const CPDF_Stream* stream) const {
351   return stream && pdfium::Contains(m_ModifiedAPStreamIDs, stream->GetObjNum());
352 }
353 
GetPageIndex(uint32_t objnum)354 int CPDF_Document::GetPageIndex(uint32_t objnum) {
355   uint32_t skip_count = 0;
356   bool bSkipped = false;
357   for (uint32_t i = 0; i < m_PageList.size(); ++i) {
358     if (m_PageList[i] == objnum)
359       return i;
360 
361     if (!bSkipped && m_PageList[i] == 0) {
362       skip_count = i;
363       bSkipped = true;
364     }
365   }
366   RetainPtr<const CPDF_Dictionary> pPages = GetPagesDict();
367   if (!pPages)
368     return -1;
369 
370   int start_index = 0;
371   int found_index = FindPageIndex(pPages, &skip_count, objnum, &start_index, 0);
372 
373   // Corrupt page tree may yield out-of-range results.
374   if (!fxcrt::IndexInBounds(m_PageList, found_index))
375     return -1;
376 
377   // Only update |m_PageList| when |objnum| points to a /Page object.
378   if (IsValidPageObject(GetOrParseIndirectObject(objnum).Get()))
379     m_PageList[found_index] = objnum;
380   return found_index;
381 }
382 
GetPageCount() const383 int CPDF_Document::GetPageCount() const {
384   return fxcrt::CollectionSize<int>(m_PageList);
385 }
386 
RetrievePageCount()387 int CPDF_Document::RetrievePageCount() {
388   RetainPtr<CPDF_Dictionary> pPages = GetMutablePagesDict();
389   if (!pPages)
390     return 0;
391 
392   if (!pPages->KeyExist("Kids"))
393     return 1;
394 
395   std::set<RetainPtr<CPDF_Dictionary>> visited_pages = {pPages};
396   return CountPages(std::move(pPages), &visited_pages).value_or(0);
397 }
398 
GetUserPermissions() const399 uint32_t CPDF_Document::GetUserPermissions() const {
400   if (m_pParser)
401     return m_pParser->GetPermissions();
402 
403   return m_pExtension ? m_pExtension->GetUserPermissions() : 0;
404 }
405 
GetFontFileStreamAcc(RetainPtr<const CPDF_Stream> pFontStream)406 RetainPtr<CPDF_StreamAcc> CPDF_Document::GetFontFileStreamAcc(
407     RetainPtr<const CPDF_Stream> pFontStream) {
408   return m_pDocPage->GetFontFileStreamAcc(std::move(pFontStream));
409 }
410 
MaybePurgeFontFileStreamAcc(RetainPtr<CPDF_StreamAcc> && pStreamAcc)411 void CPDF_Document::MaybePurgeFontFileStreamAcc(
412     RetainPtr<CPDF_StreamAcc>&& pStreamAcc) {
413   if (m_pDocPage)
414     m_pDocPage->MaybePurgeFontFileStreamAcc(std::move(pStreamAcc));
415 }
416 
MaybePurgeImage(uint32_t objnum)417 void CPDF_Document::MaybePurgeImage(uint32_t objnum) {
418   if (m_pDocPage)
419     m_pDocPage->MaybePurgeImage(objnum);
420 }
421 
CreateNewDoc()422 void CPDF_Document::CreateNewDoc() {
423   DCHECK(!m_pRootDict);
424   DCHECK(!m_pInfoDict);
425   m_pRootDict = NewIndirect<CPDF_Dictionary>();
426   m_pRootDict->SetNewFor<CPDF_Name>("Type", "Catalog");
427 
428   auto pPages = NewIndirect<CPDF_Dictionary>();
429   pPages->SetNewFor<CPDF_Name>("Type", "Pages");
430   pPages->SetNewFor<CPDF_Number>("Count", 0);
431   pPages->SetNewFor<CPDF_Array>("Kids");
432   m_pRootDict->SetNewFor<CPDF_Reference>("Pages", this, pPages->GetObjNum());
433   m_pInfoDict = NewIndirect<CPDF_Dictionary>();
434 }
435 
CreateNewPage(int iPage)436 RetainPtr<CPDF_Dictionary> CPDF_Document::CreateNewPage(int iPage) {
437   auto pDict = NewIndirect<CPDF_Dictionary>();
438   pDict->SetNewFor<CPDF_Name>("Type", "Page");
439   uint32_t dwObjNum = pDict->GetObjNum();
440   if (!InsertNewPage(iPage, pDict)) {
441     DeleteIndirectObject(dwObjNum);
442     return nullptr;
443   }
444   return pDict;
445 }
446 
InsertDeletePDFPage(RetainPtr<CPDF_Dictionary> pPages,int nPagesToGo,RetainPtr<CPDF_Dictionary> pPageDict,bool bInsert,std::set<RetainPtr<CPDF_Dictionary>> * pVisited)447 bool CPDF_Document::InsertDeletePDFPage(
448     RetainPtr<CPDF_Dictionary> pPages,
449     int nPagesToGo,
450     RetainPtr<CPDF_Dictionary> pPageDict,
451     bool bInsert,
452     std::set<RetainPtr<CPDF_Dictionary>>* pVisited) {
453   RetainPtr<CPDF_Array> pKidList = pPages->GetMutableArrayFor("Kids");
454   if (!pKidList)
455     return false;
456 
457   for (size_t i = 0; i < pKidList->size(); i++) {
458     RetainPtr<CPDF_Dictionary> pKid = pKidList->GetMutableDictAt(i);
459     if (pKid->GetNameFor("Type") == "Page") {
460       if (nPagesToGo != 0) {
461         nPagesToGo--;
462         continue;
463       }
464       if (bInsert) {
465         pKidList->InsertNewAt<CPDF_Reference>(i, this, pPageDict->GetObjNum());
466         pPageDict->SetNewFor<CPDF_Reference>("Parent", this,
467                                              pPages->GetObjNum());
468       } else {
469         pKidList->RemoveAt(i);
470       }
471       pPages->SetNewFor<CPDF_Number>(
472           "Count", pPages->GetIntegerFor("Count") + (bInsert ? 1 : -1));
473       ResetTraversal();
474       break;
475     }
476     int nPages = pKid->GetIntegerFor("Count");
477     if (nPagesToGo >= nPages) {
478       nPagesToGo -= nPages;
479       continue;
480     }
481     if (pdfium::Contains(*pVisited, pKid))
482       return false;
483 
484     ScopedSetInsertion<RetainPtr<CPDF_Dictionary>> insertion(pVisited, pKid);
485     if (!InsertDeletePDFPage(std::move(pKid), nPagesToGo, pPageDict, bInsert,
486                              pVisited)) {
487       return false;
488     }
489     pPages->SetNewFor<CPDF_Number>(
490         "Count", pPages->GetIntegerFor("Count") + (bInsert ? 1 : -1));
491     break;
492   }
493   return true;
494 }
495 
InsertNewPage(int iPage,RetainPtr<CPDF_Dictionary> pPageDict)496 bool CPDF_Document::InsertNewPage(int iPage,
497                                   RetainPtr<CPDF_Dictionary> pPageDict) {
498   RetainPtr<CPDF_Dictionary> pRoot = GetMutableRoot();
499   if (!pRoot)
500     return false;
501 
502   RetainPtr<CPDF_Dictionary> pPages = pRoot->GetMutableDictFor("Pages");
503   if (!pPages)
504     return false;
505 
506   int nPages = GetPageCount();
507   if (iPage < 0 || iPage > nPages)
508     return false;
509 
510   if (iPage == nPages) {
511     RetainPtr<CPDF_Array> pPagesList = pPages->GetOrCreateArrayFor("Kids");
512     pPagesList->AppendNew<CPDF_Reference>(this, pPageDict->GetObjNum());
513     pPages->SetNewFor<CPDF_Number>("Count", nPages + 1);
514     pPageDict->SetNewFor<CPDF_Reference>("Parent", this, pPages->GetObjNum());
515     ResetTraversal();
516   } else {
517     std::set<RetainPtr<CPDF_Dictionary>> stack = {pPages};
518     if (!InsertDeletePDFPage(std::move(pPages), iPage, pPageDict, true, &stack))
519       return false;
520   }
521   m_PageList.insert(m_PageList.begin() + iPage, pPageDict->GetObjNum());
522   return true;
523 }
524 
GetInfo()525 RetainPtr<CPDF_Dictionary> CPDF_Document::GetInfo() {
526   if (m_pInfoDict)
527     return m_pInfoDict;
528 
529   if (!m_pParser)
530     return nullptr;
531 
532   uint32_t info_obj_num = m_pParser->GetInfoObjNum();
533   if (info_obj_num == 0)
534     return nullptr;
535 
536   auto ref = pdfium::MakeRetain<CPDF_Reference>(this, info_obj_num);
537   m_pInfoDict = ToDictionary(ref->GetMutableDirect());
538   return m_pInfoDict;
539 }
540 
GetFileIdentifier() const541 RetainPtr<const CPDF_Array> CPDF_Document::GetFileIdentifier() const {
542   return m_pParser ? m_pParser->GetIDArray() : nullptr;
543 }
544 
DeletePage(int iPage)545 void CPDF_Document::DeletePage(int iPage) {
546   RetainPtr<CPDF_Dictionary> pPages = GetMutablePagesDict();
547   if (!pPages)
548     return;
549 
550   int nPages = pPages->GetIntegerFor("Count");
551   if (iPage < 0 || iPage >= nPages)
552     return;
553 
554   std::set<RetainPtr<CPDF_Dictionary>> stack = {pPages};
555   if (!InsertDeletePDFPage(std::move(pPages), iPage, nullptr, false, &stack))
556     return;
557 
558   m_PageList.erase(m_PageList.begin() + iPage);
559 }
560 
SetRootForTesting(RetainPtr<CPDF_Dictionary> root)561 void CPDF_Document::SetRootForTesting(RetainPtr<CPDF_Dictionary> root) {
562   m_pRootDict = std::move(root);
563 }
564 
ResizePageListForTesting(size_t size)565 void CPDF_Document::ResizePageListForTesting(size_t size) {
566   m_PageList.resize(size);
567 }
568 
StockFontClearer(CPDF_Document::PageDataIface * pPageData)569 CPDF_Document::StockFontClearer::StockFontClearer(
570     CPDF_Document::PageDataIface* pPageData)
571     : m_pPageData(pPageData) {}
572 
~StockFontClearer()573 CPDF_Document::StockFontClearer::~StockFontClearer() {
574   m_pPageData->ClearStockFont();
575 }
576 
577 CPDF_Document::PageDataIface::PageDataIface() = default;
578 
579 CPDF_Document::PageDataIface::~PageDataIface() = default;
580 
581 CPDF_Document::RenderDataIface::RenderDataIface() = default;
582 
583 CPDF_Document::RenderDataIface::~RenderDataIface() = default;
584