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/page/cpdf_docpagedata.h"
11 #include "core/fpdfapi/page/cpdf_pagemodule.h"
12 #include "core/fpdfapi/parser/cpdf_array.h"
13 #include "core/fpdfapi/parser/cpdf_boolean.h"
14 #include "core/fpdfapi/parser/cpdf_dictionary.h"
15 #include "core/fpdfapi/parser/cpdf_linearized_header.h"
16 #include "core/fpdfapi/parser/cpdf_name.h"
17 #include "core/fpdfapi/parser/cpdf_number.h"
18 #include "core/fpdfapi/parser/cpdf_parser.h"
19 #include "core/fpdfapi/parser/cpdf_reference.h"
20 #include "core/fpdfapi/parser/cpdf_string.h"
21 #include "core/fpdfapi/render/cpdf_docrenderdata.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "third_party/base/ptr_util.h"
24
25 namespace {
26
27 const int kNumTestPages = 7;
28
CreatePageTreeNode(RetainPtr<CPDF_Array> kids,CPDF_Document * pDoc,int count)29 CPDF_Dictionary* CreatePageTreeNode(RetainPtr<CPDF_Array> kids,
30 CPDF_Document* pDoc,
31 int count) {
32 CPDF_Array* pUnowned = pDoc->AddIndirectObject(std::move(kids))->AsArray();
33 CPDF_Dictionary* pageNode = pDoc->NewIndirect<CPDF_Dictionary>();
34 pageNode->SetNewFor<CPDF_String>("Type", "Pages", false);
35 pageNode->SetNewFor<CPDF_Reference>("Kids", pDoc, pUnowned->GetObjNum());
36 pageNode->SetNewFor<CPDF_Number>("Count", count);
37 for (size_t i = 0; i < pUnowned->size(); i++) {
38 pUnowned->GetDictAt(i)->SetNewFor<CPDF_Reference>("Parent", pDoc,
39 pageNode->GetObjNum());
40 }
41 return pageNode;
42 }
43
CreateNumberedPage(size_t number)44 RetainPtr<CPDF_Dictionary> CreateNumberedPage(size_t number) {
45 auto page = pdfium::MakeRetain<CPDF_Dictionary>();
46 page->SetNewFor<CPDF_String>("Type", "Page", false);
47 page->SetNewFor<CPDF_Number>("PageNumbering", static_cast<int>(number));
48 return page;
49 }
50
51 class CPDF_TestDocumentForPages final : public CPDF_Document {
52 public:
CPDF_TestDocumentForPages()53 CPDF_TestDocumentForPages()
54 : CPDF_Document(pdfium::MakeUnique<CPDF_DocRenderData>(),
55 pdfium::MakeUnique<CPDF_DocPageData>()) {
56 // Set up test
57 auto zeroToTwo = pdfium::MakeRetain<CPDF_Array>();
58 zeroToTwo->AddNew<CPDF_Reference>(
59 this, AddIndirectObject(CreateNumberedPage(0))->GetObjNum());
60 zeroToTwo->AddNew<CPDF_Reference>(
61 this, AddIndirectObject(CreateNumberedPage(1))->GetObjNum());
62 zeroToTwo->AddNew<CPDF_Reference>(
63 this, AddIndirectObject(CreateNumberedPage(2))->GetObjNum());
64 CPDF_Dictionary* branch1 =
65 CreatePageTreeNode(std::move(zeroToTwo), this, 3);
66
67 auto zeroToThree = pdfium::MakeRetain<CPDF_Array>();
68 zeroToThree->AddNew<CPDF_Reference>(this, branch1->GetObjNum());
69 zeroToThree->AddNew<CPDF_Reference>(
70 this, AddIndirectObject(CreateNumberedPage(3))->GetObjNum());
71 CPDF_Dictionary* branch2 =
72 CreatePageTreeNode(std::move(zeroToThree), this, 4);
73
74 auto fourFive = pdfium::MakeRetain<CPDF_Array>();
75 fourFive->AddNew<CPDF_Reference>(
76 this, AddIndirectObject(CreateNumberedPage(4))->GetObjNum());
77 fourFive->AddNew<CPDF_Reference>(
78 this, AddIndirectObject(CreateNumberedPage(5))->GetObjNum());
79 CPDF_Dictionary* branch3 = CreatePageTreeNode(std::move(fourFive), this, 2);
80
81 auto justSix = pdfium::MakeRetain<CPDF_Array>();
82 justSix->AddNew<CPDF_Reference>(
83 this, AddIndirectObject(CreateNumberedPage(6))->GetObjNum());
84 CPDF_Dictionary* branch4 = CreatePageTreeNode(std::move(justSix), this, 1);
85
86 auto allPages = pdfium::MakeRetain<CPDF_Array>();
87 allPages->AddNew<CPDF_Reference>(this, branch2->GetObjNum());
88 allPages->AddNew<CPDF_Reference>(this, branch3->GetObjNum());
89 allPages->AddNew<CPDF_Reference>(this, branch4->GetObjNum());
90 CPDF_Dictionary* pagesDict =
91 CreatePageTreeNode(std::move(allPages), this, kNumTestPages);
92
93 m_pRootDict.Reset(NewIndirect<CPDF_Dictionary>());
94 m_pRootDict->SetNewFor<CPDF_Reference>("Pages", this,
95 pagesDict->GetObjNum());
96 m_PageList.resize(kNumTestPages);
97 }
98
SetTreeSize(int size)99 void SetTreeSize(int size) {
100 m_pRootDict->SetNewFor<CPDF_Number>("Count", size);
101 m_PageList.resize(size);
102 }
103 };
104
105 class CPDF_TestDocumentWithPageWithoutPageNum final : public CPDF_Document {
106 public:
CPDF_TestDocumentWithPageWithoutPageNum()107 CPDF_TestDocumentWithPageWithoutPageNum()
108 : CPDF_Document(pdfium::MakeUnique<CPDF_DocRenderData>(),
109 pdfium::MakeUnique<CPDF_DocPageData>()) {
110 // Set up test
111 auto allPages = pdfium::MakeRetain<CPDF_Array>();
112 allPages->AddNew<CPDF_Reference>(
113 this, AddIndirectObject(CreateNumberedPage(0))->GetObjNum());
114 allPages->AddNew<CPDF_Reference>(
115 this, AddIndirectObject(CreateNumberedPage(1))->GetObjNum());
116 // Page without pageNum.
117 inlined_page_ = allPages->Add(CreateNumberedPage(2));
118 CPDF_Dictionary* pagesDict =
119 CreatePageTreeNode(std::move(allPages), this, 3);
120 m_pRootDict.Reset(NewIndirect<CPDF_Dictionary>());
121 m_pRootDict->SetNewFor<CPDF_Reference>("Pages", this,
122 pagesDict->GetObjNum());
123 m_PageList.resize(3);
124 }
125
inlined_page() const126 const CPDF_Object* inlined_page() const { return inlined_page_; }
127
128 private:
129 const CPDF_Object* inlined_page_;
130 };
131
132 class TestLinearized final : public CPDF_LinearizedHeader {
133 public:
TestLinearized(CPDF_Dictionary * dict)134 explicit TestLinearized(CPDF_Dictionary* dict)
135 : CPDF_LinearizedHeader(dict, 0) {}
136 };
137
138 class CPDF_TestDocPagesWithoutKids final : public CPDF_Document {
139 public:
CPDF_TestDocPagesWithoutKids()140 CPDF_TestDocPagesWithoutKids()
141 : CPDF_Document(pdfium::MakeUnique<CPDF_DocRenderData>(),
142 pdfium::MakeUnique<CPDF_DocPageData>()) {
143 CPDF_Dictionary* pagesDict = NewIndirect<CPDF_Dictionary>();
144 pagesDict->SetNewFor<CPDF_Name>("Type", "Pages");
145 pagesDict->SetNewFor<CPDF_Number>("Count", 3);
146 m_PageList.resize(10);
147 m_pRootDict.Reset(NewIndirect<CPDF_Dictionary>());
148 m_pRootDict->SetNewFor<CPDF_Reference>("Pages", this,
149 pagesDict->GetObjNum());
150 }
151 };
152
153 class CPDF_TestDocumentAllowSetParser final : public CPDF_Document {
154 public:
CPDF_TestDocumentAllowSetParser()155 CPDF_TestDocumentAllowSetParser()
156 : CPDF_Document(pdfium::MakeUnique<CPDF_DocRenderData>(),
157 pdfium::MakeUnique<CPDF_DocPageData>()) {}
158
159 using CPDF_Document::SetParser;
160 };
161
162 } // namespace
163
164 class cpdf_document_test : public testing::Test {
165 public:
SetUp()166 void SetUp() override { CPDF_PageModule::Create(); }
TearDown()167 void TearDown() override { CPDF_PageModule::Destroy(); }
168 };
169
TEST_F(cpdf_document_test,GetPages)170 TEST_F(cpdf_document_test, GetPages) {
171 std::unique_ptr<CPDF_TestDocumentForPages> document =
172 pdfium::MakeUnique<CPDF_TestDocumentForPages>();
173 for (int i = 0; i < kNumTestPages; i++) {
174 CPDF_Dictionary* page = document->GetPageDictionary(i);
175 ASSERT_TRUE(page);
176 ASSERT_TRUE(page->KeyExist("PageNumbering"));
177 EXPECT_EQ(i, page->GetIntegerFor("PageNumbering"));
178 }
179 CPDF_Dictionary* page = document->GetPageDictionary(kNumTestPages);
180 EXPECT_FALSE(page);
181 }
182
TEST_F(cpdf_document_test,GetPageWithoutObjNumTwice)183 TEST_F(cpdf_document_test, GetPageWithoutObjNumTwice) {
184 auto document = pdfium::MakeUnique<CPDF_TestDocumentWithPageWithoutPageNum>();
185 CPDF_Dictionary* page = document->GetPageDictionary(2);
186 ASSERT_TRUE(page);
187 ASSERT_EQ(document->inlined_page(), page);
188
189 CPDF_Dictionary* second_call_page = document->GetPageDictionary(2);
190 EXPECT_TRUE(second_call_page);
191 EXPECT_EQ(page, second_call_page);
192 }
193
TEST_F(cpdf_document_test,GetPagesReverseOrder)194 TEST_F(cpdf_document_test, GetPagesReverseOrder) {
195 std::unique_ptr<CPDF_TestDocumentForPages> document =
196 pdfium::MakeUnique<CPDF_TestDocumentForPages>();
197 for (int i = 6; i >= 0; i--) {
198 CPDF_Dictionary* page = document->GetPageDictionary(i);
199 ASSERT_TRUE(page);
200 ASSERT_TRUE(page->KeyExist("PageNumbering"));
201 EXPECT_EQ(i, page->GetIntegerFor("PageNumbering"));
202 }
203 CPDF_Dictionary* page = document->GetPageDictionary(kNumTestPages);
204 EXPECT_FALSE(page);
205 }
206
TEST_F(cpdf_document_test,GetPagesInDisorder)207 TEST_F(cpdf_document_test, GetPagesInDisorder) {
208 std::unique_ptr<CPDF_TestDocumentForPages> document =
209 pdfium::MakeUnique<CPDF_TestDocumentForPages>();
210
211 CPDF_Dictionary* page = document->GetPageDictionary(1);
212 ASSERT_TRUE(page);
213 ASSERT_TRUE(page->KeyExist("PageNumbering"));
214 EXPECT_EQ(1, page->GetIntegerFor("PageNumbering"));
215
216 page = document->GetPageDictionary(3);
217 ASSERT_TRUE(page);
218 ASSERT_TRUE(page->KeyExist("PageNumbering"));
219 EXPECT_EQ(3, page->GetIntegerFor("PageNumbering"));
220
221 page = document->GetPageDictionary(kNumTestPages);
222 EXPECT_FALSE(page);
223
224 page = document->GetPageDictionary(6);
225 ASSERT_TRUE(page);
226 ASSERT_TRUE(page->KeyExist("PageNumbering"));
227 EXPECT_EQ(6, page->GetIntegerFor("PageNumbering"));
228 }
229
TEST_F(cpdf_document_test,UseCachedPageObjNumIfHaveNotPagesDict)230 TEST_F(cpdf_document_test, UseCachedPageObjNumIfHaveNotPagesDict) {
231 // ObjNum can be added in CPDF_DataAvail::IsPageAvail, and PagesDict
232 // can be not exists in this case.
233 // (case, when hint table is used to page check in CPDF_DataAvail).
234 auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
235 dict->SetNewFor<CPDF_Boolean>("Linearized", true);
236 const int page_count = 100;
237 dict->SetNewFor<CPDF_Number>("N", page_count);
238 auto linearized = pdfium::MakeUnique<TestLinearized>(dict.Get());
239 auto parser = pdfium::MakeUnique<CPDF_Parser>();
240 parser->SetLinearizedHeader(std::move(linearized));
241 CPDF_TestDocumentAllowSetParser document;
242 document.SetParser(std::move(parser));
243 document.LoadPages();
244 ASSERT_EQ(page_count, document.GetPageCount());
245 CPDF_Object* page_stub = document.NewIndirect<CPDF_Dictionary>();
246 const uint32_t obj_num = page_stub->GetObjNum();
247 const int test_page_num = 33;
248
249 EXPECT_FALSE(document.IsPageLoaded(test_page_num));
250 EXPECT_EQ(nullptr, document.GetPageDictionary(test_page_num));
251
252 document.SetPageObjNum(test_page_num, obj_num);
253 EXPECT_TRUE(document.IsPageLoaded(test_page_num));
254 EXPECT_EQ(page_stub, document.GetPageDictionary(test_page_num));
255 }
256
TEST_F(cpdf_document_test,CountGreaterThanPageTree)257 TEST_F(cpdf_document_test, CountGreaterThanPageTree) {
258 std::unique_ptr<CPDF_TestDocumentForPages> document =
259 pdfium::MakeUnique<CPDF_TestDocumentForPages>();
260 document->SetTreeSize(kNumTestPages + 3);
261 for (int i = 0; i < kNumTestPages; i++)
262 EXPECT_TRUE(document->GetPageDictionary(i));
263 for (int i = kNumTestPages; i < kNumTestPages + 4; i++)
264 EXPECT_FALSE(document->GetPageDictionary(i));
265 EXPECT_TRUE(document->GetPageDictionary(kNumTestPages - 1));
266 }
267
TEST_F(cpdf_document_test,PagesWithoutKids)268 TEST_F(cpdf_document_test, PagesWithoutKids) {
269 // Set up a document with Pages dict without kids, and Count = 3
270 auto pDoc = pdfium::MakeUnique<CPDF_TestDocPagesWithoutKids>();
271 EXPECT_TRUE(pDoc->GetPageDictionary(0));
272 // Test GetPage does not fetch pages out of range
273 for (int i = 1; i < 5; i++)
274 EXPECT_FALSE(pDoc->GetPageDictionary(i));
275
276 EXPECT_TRUE(pDoc->GetPageDictionary(0));
277 }
278