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