1 // Copyright 2016 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "core/fpdfapi/parser/cpdf_document.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "core/fpdfapi/cpdf_modulemgr.h"
11 #include "core/fpdfapi/parser/cpdf_array.h"
12 #include "core/fpdfapi/parser/cpdf_boolean.h"
13 #include "core/fpdfapi/parser/cpdf_dictionary.h"
14 #include "core/fpdfapi/parser/cpdf_linearized_header.h"
15 #include "core/fpdfapi/parser/cpdf_number.h"
16 #include "core/fpdfapi/parser/cpdf_parser.h"
17 #include "core/fpdfapi/parser/cpdf_reference.h"
18 #include "core/fpdfapi/parser/cpdf_string.h"
19 #include "core/fxcrt/fx_memory.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "third_party/base/ptr_util.h"
22
23 namespace {
24
CreatePageTreeNode(std::unique_ptr<CPDF_Array> kids,CPDF_Document * pDoc,int count)25 CPDF_Dictionary* CreatePageTreeNode(std::unique_ptr<CPDF_Array> kids,
26 CPDF_Document* pDoc,
27 int count) {
28 CPDF_Array* pUnowned = pDoc->AddIndirectObject(std::move(kids))->AsArray();
29 CPDF_Dictionary* pageNode = pDoc->NewIndirect<CPDF_Dictionary>();
30 pageNode->SetNewFor<CPDF_String>("Type", "Pages", false);
31 pageNode->SetNewFor<CPDF_Reference>("Kids", pDoc, pUnowned->GetObjNum());
32 pageNode->SetNewFor<CPDF_Number>("Count", count);
33 for (size_t i = 0; i < pUnowned->GetCount(); i++) {
34 pUnowned->GetDictAt(i)->SetNewFor<CPDF_Reference>("Parent", pDoc,
35 pageNode->GetObjNum());
36 }
37 return pageNode;
38 }
39
CreateNumberedPage(size_t number)40 std::unique_ptr<CPDF_Dictionary> CreateNumberedPage(size_t number) {
41 auto page = pdfium::MakeUnique<CPDF_Dictionary>();
42 page->SetNewFor<CPDF_String>("Type", "Page", false);
43 page->SetNewFor<CPDF_Number>("PageNumbering", static_cast<int>(number));
44 return page;
45 }
46
47 class CPDF_TestDocumentForPages : public CPDF_Document {
48 public:
CPDF_TestDocumentForPages()49 CPDF_TestDocumentForPages() : CPDF_Document(nullptr) {
50 // Set up test
51 auto zeroToTwo = pdfium::MakeUnique<CPDF_Array>();
52 zeroToTwo->AddNew<CPDF_Reference>(
53 this, AddIndirectObject(CreateNumberedPage(0))->GetObjNum());
54 zeroToTwo->AddNew<CPDF_Reference>(
55 this, AddIndirectObject(CreateNumberedPage(1))->GetObjNum());
56 zeroToTwo->AddNew<CPDF_Reference>(
57 this, AddIndirectObject(CreateNumberedPage(2))->GetObjNum());
58 CPDF_Dictionary* branch1 =
59 CreatePageTreeNode(std::move(zeroToTwo), this, 3);
60
61 auto zeroToThree = pdfium::MakeUnique<CPDF_Array>();
62 zeroToThree->AddNew<CPDF_Reference>(this, branch1->GetObjNum());
63 zeroToThree->AddNew<CPDF_Reference>(
64 this, AddIndirectObject(CreateNumberedPage(3))->GetObjNum());
65 CPDF_Dictionary* branch2 =
66 CreatePageTreeNode(std::move(zeroToThree), this, 4);
67
68 auto fourFive = pdfium::MakeUnique<CPDF_Array>();
69 fourFive->AddNew<CPDF_Reference>(
70 this, AddIndirectObject(CreateNumberedPage(4))->GetObjNum());
71 fourFive->AddNew<CPDF_Reference>(
72 this, AddIndirectObject(CreateNumberedPage(5))->GetObjNum());
73 CPDF_Dictionary* branch3 = CreatePageTreeNode(std::move(fourFive), this, 2);
74
75 auto justSix = pdfium::MakeUnique<CPDF_Array>();
76 justSix->AddNew<CPDF_Reference>(
77 this, AddIndirectObject(CreateNumberedPage(6))->GetObjNum());
78 CPDF_Dictionary* branch4 = CreatePageTreeNode(std::move(justSix), this, 1);
79
80 auto allPages = pdfium::MakeUnique<CPDF_Array>();
81 allPages->AddNew<CPDF_Reference>(this, branch2->GetObjNum());
82 allPages->AddNew<CPDF_Reference>(this, branch3->GetObjNum());
83 allPages->AddNew<CPDF_Reference>(this, branch4->GetObjNum());
84 CPDF_Dictionary* pagesDict =
85 CreatePageTreeNode(std::move(allPages), this, 7);
86
87 m_pOwnedRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
88 m_pOwnedRootDict->SetNewFor<CPDF_Reference>("Pages", this,
89 pagesDict->GetObjNum());
90 m_pRootDict = m_pOwnedRootDict.get();
91 m_PageList.resize(7);
92 }
93
94 private:
95 std::unique_ptr<CPDF_Dictionary> m_pOwnedRootDict;
96 };
97
98 class CPDF_TestDocumentWithPageWithoutPageNum : public CPDF_Document {
99 public:
CPDF_TestDocumentWithPageWithoutPageNum()100 CPDF_TestDocumentWithPageWithoutPageNum() : CPDF_Document(nullptr) {
101 // Set up test
102 auto allPages = pdfium::MakeUnique<CPDF_Array>();
103 allPages->AddNew<CPDF_Reference>(
104 this, AddIndirectObject(CreateNumberedPage(0))->GetObjNum());
105 allPages->AddNew<CPDF_Reference>(
106 this, AddIndirectObject(CreateNumberedPage(1))->GetObjNum());
107 // Page without pageNum.
108 allPages->Add(CreateNumberedPage(2));
109 CPDF_Dictionary* pagesDict =
110 CreatePageTreeNode(std::move(allPages), this, 3);
111 m_pOwnedRootDict = pdfium::MakeUnique<CPDF_Dictionary>();
112 m_pOwnedRootDict->SetNewFor<CPDF_Reference>("Pages", this,
113 pagesDict->GetObjNum());
114 m_pRootDict = m_pOwnedRootDict.get();
115 m_PageList.resize(3);
116 }
117
118 private:
119 std::unique_ptr<CPDF_Dictionary> m_pOwnedRootDict;
120 };
121
122 class TestLinearized : public CPDF_LinearizedHeader {
123 public:
TestLinearized(CPDF_Dictionary * dict)124 explicit TestLinearized(CPDF_Dictionary* dict)
125 : CPDF_LinearizedHeader(dict) {}
126 };
127 } // namespace
128
129 class cpdf_document_test : public testing::Test {
130 public:
SetUp()131 void SetUp() override {
132 CPDF_ModuleMgr* module_mgr = CPDF_ModuleMgr::Get();
133 module_mgr->InitPageModule();
134 }
TearDown()135 void TearDown() override { CPDF_ModuleMgr::Destroy(); }
136 };
137
TEST_F(cpdf_document_test,GetPages)138 TEST_F(cpdf_document_test, GetPages) {
139 std::unique_ptr<CPDF_TestDocumentForPages> document =
140 pdfium::MakeUnique<CPDF_TestDocumentForPages>();
141 for (int i = 0; i < 7; i++) {
142 CPDF_Dictionary* page = document->GetPage(i);
143 ASSERT_TRUE(page);
144 ASSERT_TRUE(page->KeyExist("PageNumbering"));
145 EXPECT_EQ(i, page->GetIntegerFor("PageNumbering"));
146 }
147 CPDF_Dictionary* page = document->GetPage(7);
148 EXPECT_FALSE(page);
149 }
150
TEST_F(cpdf_document_test,GetPageWithoutObjNumTwice)151 TEST_F(cpdf_document_test, GetPageWithoutObjNumTwice) {
152 std::unique_ptr<CPDF_TestDocumentWithPageWithoutPageNum> document =
153 pdfium::MakeUnique<CPDF_TestDocumentWithPageWithoutPageNum>();
154 const CPDF_Dictionary* page = document->GetPage(2);
155 ASSERT_TRUE(page);
156 // This is page without obj num.
157 ASSERT_EQ(0ul, page->GetObjNum());
158 const CPDF_Dictionary* second_call_page = document->GetPage(2);
159 EXPECT_TRUE(second_call_page);
160 EXPECT_EQ(page, second_call_page);
161 }
162
TEST_F(cpdf_document_test,GetPagesReverseOrder)163 TEST_F(cpdf_document_test, GetPagesReverseOrder) {
164 std::unique_ptr<CPDF_TestDocumentForPages> document =
165 pdfium::MakeUnique<CPDF_TestDocumentForPages>();
166 for (int i = 6; i >= 0; i--) {
167 CPDF_Dictionary* page = document->GetPage(i);
168 ASSERT_TRUE(page);
169 ASSERT_TRUE(page->KeyExist("PageNumbering"));
170 EXPECT_EQ(i, page->GetIntegerFor("PageNumbering"));
171 }
172 CPDF_Dictionary* page = document->GetPage(7);
173 EXPECT_FALSE(page);
174 }
175
TEST_F(cpdf_document_test,GetPagesInDisorder)176 TEST_F(cpdf_document_test, GetPagesInDisorder) {
177 std::unique_ptr<CPDF_TestDocumentForPages> document =
178 pdfium::MakeUnique<CPDF_TestDocumentForPages>();
179
180 CPDF_Dictionary* page = document->GetPage(1);
181 ASSERT_TRUE(page);
182 ASSERT_TRUE(page->KeyExist("PageNumbering"));
183 EXPECT_EQ(1, page->GetIntegerFor("PageNumbering"));
184
185 page = document->GetPage(3);
186 ASSERT_TRUE(page);
187 ASSERT_TRUE(page->KeyExist("PageNumbering"));
188 EXPECT_EQ(3, page->GetIntegerFor("PageNumbering"));
189
190 page = document->GetPage(7);
191 EXPECT_FALSE(page);
192
193 page = document->GetPage(6);
194 ASSERT_TRUE(page);
195 ASSERT_TRUE(page->KeyExist("PageNumbering"));
196 EXPECT_EQ(6, page->GetIntegerFor("PageNumbering"));
197 }
198
TEST_F(cpdf_document_test,UseCachedPageObjNumIfHaveNotPagesDict)199 TEST_F(cpdf_document_test, UseCachedPageObjNumIfHaveNotPagesDict) {
200 // ObjNum can be added in CPDF_DataAvail::IsPageAvail, and PagesDict
201 // can be not exists in this case.
202 // (case, when hint table is used to page check in CPDF_DataAvail).
203 CPDF_Document document(pdfium::MakeUnique<CPDF_Parser>());
204 auto dict = pdfium::MakeUnique<CPDF_Dictionary>();
205 dict->SetNewFor<CPDF_Boolean>("Linearized", true);
206 const int page_count = 100;
207 dict->SetNewFor<CPDF_Number>("N", page_count);
208 TestLinearized linearized(dict.get());
209 document.LoadLinearizedDoc(&linearized);
210 ASSERT_EQ(page_count, document.GetPageCount());
211 CPDF_Object* page_stub = document.NewIndirect<CPDF_Dictionary>();
212 const uint32_t obj_num = page_stub->GetObjNum();
213 const int test_page_num = 33;
214
215 EXPECT_FALSE(document.IsPageLoaded(test_page_num));
216 EXPECT_EQ(nullptr, document.GetPage(test_page_num));
217
218 document.SetPageObjNum(test_page_num, obj_num);
219 EXPECT_TRUE(document.IsPageLoaded(test_page_num));
220 EXPECT_EQ(page_stub, document.GetPage(test_page_num));
221 }
222