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