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 "public/fpdf_doc.h"
6
7 #include <memory>
8 #include <vector>
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_dictionary.h"
14 #include "core/fpdfapi/parser/cpdf_document.h"
15 #include "core/fpdfapi/parser/cpdf_name.h"
16 #include "core/fpdfapi/parser/cpdf_null.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 "core/fpdfdoc/cpdf_dest.h"
23 #include "fpdfsdk/cpdfsdk_helpers.h"
24 #include "public/cpp/fpdf_scopers.h"
25 #include "testing/fx_string_testhelpers.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 #include "third_party/base/ptr_util.h"
28
29 class CPDF_TestDocument final : public CPDF_Document {
30 public:
CPDF_TestDocument()31 CPDF_TestDocument()
32 : CPDF_Document(pdfium::MakeUnique<CPDF_DocRenderData>(),
33 pdfium::MakeUnique<CPDF_DocPageData>()) {}
34
SetRoot(CPDF_Dictionary * root)35 void SetRoot(CPDF_Dictionary* root) { m_pRootDict.Reset(root); }
GetHolder()36 CPDF_IndirectObjectHolder* GetHolder() { return this; }
37 };
38
39 class PDFDocTest : public testing::Test {
40 public:
41 struct DictObjInfo {
42 uint32_t num;
43 CPDF_Dictionary* obj;
44 };
45
SetUp()46 void SetUp() override {
47 CPDF_PageModule::Create();
48 auto pTestDoc = pdfium::MakeUnique<CPDF_TestDocument>();
49 m_pIndirectObjs = pTestDoc->GetHolder();
50 m_pRootObj.Reset(m_pIndirectObjs->NewIndirect<CPDF_Dictionary>());
51 pTestDoc->SetRoot(m_pRootObj.Get());
52 m_pDoc.reset(FPDFDocumentFromCPDFDocument(pTestDoc.release()));
53 }
54
TearDown()55 void TearDown() override {
56 m_pRootObj = nullptr;
57 m_pIndirectObjs = nullptr;
58 m_pDoc.reset();
59 CPDF_PageModule::Destroy();
60 }
61
CreateDictObjs(int num)62 std::vector<DictObjInfo> CreateDictObjs(int num) {
63 std::vector<DictObjInfo> info;
64 for (int i = 0; i < num; ++i) {
65 // Objects created will be released by the document.
66 CPDF_Dictionary* obj = m_pIndirectObjs->NewIndirect<CPDF_Dictionary>();
67 info.push_back({obj->GetObjNum(), obj});
68 }
69 return info;
70 }
71
72 protected:
73 ScopedFPDFDocument m_pDoc;
74 UnownedPtr<CPDF_IndirectObjectHolder> m_pIndirectObjs;
75 RetainPtr<CPDF_Dictionary> m_pRootObj;
76 };
77
TEST_F(PDFDocTest,FindBookmark)78 TEST_F(PDFDocTest, FindBookmark) {
79 {
80 // No bookmark information.
81 ScopedFPDFWideString title = GetFPDFWideString(L"");
82 EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
83
84 title = GetFPDFWideString(L"Preface");
85 EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
86 }
87 {
88 // Empty bookmark tree.
89 m_pRootObj->SetNewFor<CPDF_Dictionary>("Outlines");
90 ScopedFPDFWideString title = GetFPDFWideString(L"");
91 EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
92
93 title = GetFPDFWideString(L"Preface");
94 EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
95 }
96 {
97 // Check on a regular bookmark tree.
98 auto bookmarks = CreateDictObjs(3);
99
100 bookmarks[1].obj->SetNewFor<CPDF_String>("Title", L"Chapter 1");
101 bookmarks[1].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
102 bookmarks[0].num);
103 bookmarks[1].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(),
104 bookmarks[2].num);
105
106 bookmarks[2].obj->SetNewFor<CPDF_String>("Title", L"Chapter 2");
107 bookmarks[2].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
108 bookmarks[0].num);
109 bookmarks[2].obj->SetNewFor<CPDF_Reference>("Prev", m_pIndirectObjs.Get(),
110 bookmarks[1].num);
111
112 bookmarks[0].obj->SetNewFor<CPDF_Name>("Type", "Outlines");
113 bookmarks[0].obj->SetNewFor<CPDF_Number>("Count", 2);
114 bookmarks[0].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
115 bookmarks[1].num);
116 bookmarks[0].obj->SetNewFor<CPDF_Reference>("Last", m_pIndirectObjs.Get(),
117 bookmarks[2].num);
118
119 m_pRootObj->SetNewFor<CPDF_Reference>("Outlines", m_pIndirectObjs.Get(),
120 bookmarks[0].num);
121
122 // Title with no match.
123 ScopedFPDFWideString title = GetFPDFWideString(L"Chapter 3");
124 EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
125
126 // Title with partial match only.
127 title = GetFPDFWideString(L"Chapter");
128 EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
129
130 // Title with a match.
131 title = GetFPDFWideString(L"Chapter 2");
132 EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[2].obj),
133 FPDFBookmark_Find(m_pDoc.get(), title.get()));
134
135 // Title match is case insensitive.
136 title = GetFPDFWideString(L"cHaPter 2");
137 EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[2].obj),
138 FPDFBookmark_Find(m_pDoc.get(), title.get()));
139 }
140 {
141 // Circular bookmarks in depth.
142 auto bookmarks = CreateDictObjs(3);
143
144 bookmarks[1].obj->SetNewFor<CPDF_String>("Title", L"Chapter 1");
145 bookmarks[1].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
146 bookmarks[0].num);
147 bookmarks[1].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
148 bookmarks[2].num);
149
150 bookmarks[2].obj->SetNewFor<CPDF_String>("Title", L"Chapter 2");
151 bookmarks[2].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
152 bookmarks[1].num);
153 bookmarks[2].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
154 bookmarks[1].num);
155
156 bookmarks[0].obj->SetNewFor<CPDF_Name>("Type", "Outlines");
157 bookmarks[0].obj->SetNewFor<CPDF_Number>("Count", 2);
158 bookmarks[0].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
159 bookmarks[1].num);
160 bookmarks[0].obj->SetNewFor<CPDF_Reference>("Last", m_pIndirectObjs.Get(),
161 bookmarks[2].num);
162
163 m_pRootObj->SetNewFor<CPDF_Reference>("Outlines", m_pIndirectObjs.Get(),
164 bookmarks[0].num);
165
166 // Title with no match.
167 ScopedFPDFWideString title = GetFPDFWideString(L"Chapter 3");
168 EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
169
170 // Title with a match.
171 title = GetFPDFWideString(L"Chapter 2");
172 EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[2].obj),
173 FPDFBookmark_Find(m_pDoc.get(), title.get()));
174 }
175 {
176 // Circular bookmarks in breadth.
177 auto bookmarks = CreateDictObjs(4);
178
179 bookmarks[1].obj->SetNewFor<CPDF_String>("Title", L"Chapter 1");
180 bookmarks[1].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
181 bookmarks[0].num);
182 bookmarks[1].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(),
183 bookmarks[2].num);
184
185 bookmarks[2].obj->SetNewFor<CPDF_String>("Title", L"Chapter 2");
186 bookmarks[2].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
187 bookmarks[0].num);
188 bookmarks[2].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(),
189 bookmarks[3].num);
190
191 bookmarks[3].obj->SetNewFor<CPDF_String>("Title", L"Chapter 3");
192 bookmarks[3].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(),
193 bookmarks[0].num);
194 bookmarks[3].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(),
195 bookmarks[1].num);
196
197 bookmarks[0].obj->SetNewFor<CPDF_Name>("Type", "Outlines");
198 bookmarks[0].obj->SetNewFor<CPDF_Number>("Count", 2);
199 bookmarks[0].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(),
200 bookmarks[1].num);
201 bookmarks[0].obj->SetNewFor<CPDF_Reference>("Last", m_pIndirectObjs.Get(),
202 bookmarks[2].num);
203
204 m_pRootObj->SetNewFor<CPDF_Reference>("Outlines", m_pIndirectObjs.Get(),
205 bookmarks[0].num);
206
207 // Title with no match.
208 ScopedFPDFWideString title = GetFPDFWideString(L"Chapter 8");
209 EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get()));
210
211 // Title with a match.
212 title = GetFPDFWideString(L"Chapter 3");
213 EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[3].obj),
214 FPDFBookmark_Find(m_pDoc.get(), title.get()));
215 }
216 }
217
TEST_F(PDFDocTest,GetLocationInPage)218 TEST_F(PDFDocTest, GetLocationInPage) {
219 auto array = pdfium::MakeRetain<CPDF_Array>();
220 array->AddNew<CPDF_Number>(0); // Page Index.
221 array->AddNew<CPDF_Name>("XYZ");
222 array->AddNew<CPDF_Number>(4); // X
223 array->AddNew<CPDF_Number>(5); // Y
224 array->AddNew<CPDF_Number>(6); // Zoom.
225
226 FPDF_BOOL hasX;
227 FPDF_BOOL hasY;
228 FPDF_BOOL hasZoom;
229 FS_FLOAT x;
230 FS_FLOAT y;
231 FS_FLOAT zoom;
232
233 EXPECT_TRUE(FPDFDest_GetLocationInPage(FPDFDestFromCPDFArray(array.Get()),
234 &hasX, &hasY, &hasZoom, &x, &y,
235 &zoom));
236 EXPECT_TRUE(hasX);
237 EXPECT_TRUE(hasY);
238 EXPECT_TRUE(hasZoom);
239 EXPECT_EQ(4, x);
240 EXPECT_EQ(5, y);
241 EXPECT_EQ(6, zoom);
242
243 array->SetNewAt<CPDF_Null>(2);
244 array->SetNewAt<CPDF_Null>(3);
245 array->SetNewAt<CPDF_Null>(4);
246 EXPECT_TRUE(FPDFDest_GetLocationInPage(FPDFDestFromCPDFArray(array.Get()),
247 &hasX, &hasY, &hasZoom, &x, &y,
248 &zoom));
249 EXPECT_FALSE(hasX);
250 EXPECT_FALSE(hasY);
251 EXPECT_FALSE(hasZoom);
252
253 array = pdfium::MakeRetain<CPDF_Array>();
254 EXPECT_FALSE(FPDFDest_GetLocationInPage(FPDFDestFromCPDFArray(array.Get()),
255 &hasX, &hasY, &hasZoom, &x, &y,
256 &zoom));
257 }
258