| // Copyright 2016 PDFium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "public/fpdf_doc.h" | |
| #include <memory> | |
| #include <vector> | |
| #include "core/fpdfapi/cpdf_modulemgr.h" | |
| #include "core/fpdfapi/parser/cpdf_array.h" | |
| #include "core/fpdfapi/parser/cpdf_document.h" | |
| #include "core/fpdfapi/parser/cpdf_name.h" | |
| #include "core/fpdfapi/parser/cpdf_null.h" | |
| #include "core/fpdfapi/parser/cpdf_number.h" | |
| #include "core/fpdfapi/parser/cpdf_parser.h" | |
| #include "core/fpdfapi/parser/cpdf_reference.h" | |
| #include "core/fpdfapi/parser/cpdf_string.h" | |
| #include "core/fpdfdoc/cpdf_dest.h" | |
| #include "fpdfsdk/cpdfsdk_helpers.h" | |
| #include "public/cpp/fpdf_scopers.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #include "testing/test_support.h" | |
| #include "third_party/base/ptr_util.h" | |
| class CPDF_TestDocument final : public CPDF_Document { | |
| public: | |
| CPDF_TestDocument() : CPDF_Document() {} | |
| void SetRoot(CPDF_Dictionary* root) { m_pRootDict = root; } | |
| CPDF_IndirectObjectHolder* GetHolder() { return this; } | |
| }; | |
| class PDFDocTest : public testing::Test { | |
| public: | |
| struct DictObjInfo { | |
| uint32_t num; | |
| CPDF_Dictionary* obj; | |
| }; | |
| void SetUp() override { | |
| CPDF_ModuleMgr::Get()->Init(); | |
| auto pTestDoc = pdfium::MakeUnique<CPDF_TestDocument>(); | |
| m_pIndirectObjs = pTestDoc->GetHolder(); | |
| m_pRootObj = m_pIndirectObjs->NewIndirect<CPDF_Dictionary>(); | |
| pTestDoc->SetRoot(m_pRootObj.Get()); | |
| m_pDoc.reset(FPDFDocumentFromCPDFDocument(pTestDoc.release())); | |
| } | |
| void TearDown() override { | |
| m_pRootObj = nullptr; | |
| m_pIndirectObjs = nullptr; | |
| m_pDoc.reset(); | |
| CPDF_ModuleMgr::Destroy(); | |
| } | |
| std::vector<DictObjInfo> CreateDictObjs(int num) { | |
| std::vector<DictObjInfo> info; | |
| for (int i = 0; i < num; ++i) { | |
| // Objects created will be released by the document. | |
| CPDF_Dictionary* obj = m_pIndirectObjs->NewIndirect<CPDF_Dictionary>(); | |
| info.push_back({obj->GetObjNum(), obj}); | |
| } | |
| return info; | |
| } | |
| protected: | |
| ScopedFPDFDocument m_pDoc; | |
| UnownedPtr<CPDF_IndirectObjectHolder> m_pIndirectObjs; | |
| UnownedPtr<CPDF_Dictionary> m_pRootObj; | |
| }; | |
| TEST_F(PDFDocTest, FindBookmark) { | |
| { | |
| // No bookmark information. | |
| std::unique_ptr<unsigned short, pdfium::FreeDeleter> title = | |
| GetFPDFWideString(L""); | |
| EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| title = GetFPDFWideString(L"Preface"); | |
| EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| } | |
| { | |
| // Empty bookmark tree. | |
| m_pRootObj->SetNewFor<CPDF_Dictionary>("Outlines"); | |
| std::unique_ptr<unsigned short, pdfium::FreeDeleter> title = | |
| GetFPDFWideString(L""); | |
| EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| title = GetFPDFWideString(L"Preface"); | |
| EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| } | |
| { | |
| // Check on a regular bookmark tree. | |
| auto bookmarks = CreateDictObjs(3); | |
| bookmarks[1].obj->SetNewFor<CPDF_String>("Title", L"Chapter 1"); | |
| bookmarks[1].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(), | |
| bookmarks[0].num); | |
| bookmarks[1].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(), | |
| bookmarks[2].num); | |
| bookmarks[2].obj->SetNewFor<CPDF_String>("Title", L"Chapter 2"); | |
| bookmarks[2].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(), | |
| bookmarks[0].num); | |
| bookmarks[2].obj->SetNewFor<CPDF_Reference>("Prev", m_pIndirectObjs.Get(), | |
| bookmarks[1].num); | |
| bookmarks[0].obj->SetNewFor<CPDF_Name>("Type", "Outlines"); | |
| bookmarks[0].obj->SetNewFor<CPDF_Number>("Count", 2); | |
| bookmarks[0].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(), | |
| bookmarks[1].num); | |
| bookmarks[0].obj->SetNewFor<CPDF_Reference>("Last", m_pIndirectObjs.Get(), | |
| bookmarks[2].num); | |
| m_pRootObj->SetNewFor<CPDF_Reference>("Outlines", m_pIndirectObjs.Get(), | |
| bookmarks[0].num); | |
| // Title with no match. | |
| std::unique_ptr<unsigned short, pdfium::FreeDeleter> title = | |
| GetFPDFWideString(L"Chapter 3"); | |
| EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| // Title with partial match only. | |
| title = GetFPDFWideString(L"Chapter"); | |
| EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| // Title with a match. | |
| title = GetFPDFWideString(L"Chapter 2"); | |
| EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[2].obj), | |
| FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| // Title match is case insensitive. | |
| title = GetFPDFWideString(L"cHaPter 2"); | |
| EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[2].obj), | |
| FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| } | |
| { | |
| // Circular bookmarks in depth. | |
| auto bookmarks = CreateDictObjs(3); | |
| bookmarks[1].obj->SetNewFor<CPDF_String>("Title", L"Chapter 1"); | |
| bookmarks[1].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(), | |
| bookmarks[0].num); | |
| bookmarks[1].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(), | |
| bookmarks[2].num); | |
| bookmarks[2].obj->SetNewFor<CPDF_String>("Title", L"Chapter 2"); | |
| bookmarks[2].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(), | |
| bookmarks[1].num); | |
| bookmarks[2].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(), | |
| bookmarks[1].num); | |
| bookmarks[0].obj->SetNewFor<CPDF_Name>("Type", "Outlines"); | |
| bookmarks[0].obj->SetNewFor<CPDF_Number>("Count", 2); | |
| bookmarks[0].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(), | |
| bookmarks[1].num); | |
| bookmarks[0].obj->SetNewFor<CPDF_Reference>("Last", m_pIndirectObjs.Get(), | |
| bookmarks[2].num); | |
| m_pRootObj->SetNewFor<CPDF_Reference>("Outlines", m_pIndirectObjs.Get(), | |
| bookmarks[0].num); | |
| // Title with no match. | |
| std::unique_ptr<unsigned short, pdfium::FreeDeleter> title = | |
| GetFPDFWideString(L"Chapter 3"); | |
| EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| // Title with a match. | |
| title = GetFPDFWideString(L"Chapter 2"); | |
| EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[2].obj), | |
| FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| } | |
| { | |
| // Circular bookmarks in breadth. | |
| auto bookmarks = CreateDictObjs(4); | |
| bookmarks[1].obj->SetNewFor<CPDF_String>("Title", L"Chapter 1"); | |
| bookmarks[1].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(), | |
| bookmarks[0].num); | |
| bookmarks[1].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(), | |
| bookmarks[2].num); | |
| bookmarks[2].obj->SetNewFor<CPDF_String>("Title", L"Chapter 2"); | |
| bookmarks[2].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(), | |
| bookmarks[0].num); | |
| bookmarks[2].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(), | |
| bookmarks[3].num); | |
| bookmarks[3].obj->SetNewFor<CPDF_String>("Title", L"Chapter 3"); | |
| bookmarks[3].obj->SetNewFor<CPDF_Reference>("Parent", m_pIndirectObjs.Get(), | |
| bookmarks[0].num); | |
| bookmarks[3].obj->SetNewFor<CPDF_Reference>("Next", m_pIndirectObjs.Get(), | |
| bookmarks[1].num); | |
| bookmarks[0].obj->SetNewFor<CPDF_Name>("Type", "Outlines"); | |
| bookmarks[0].obj->SetNewFor<CPDF_Number>("Count", 2); | |
| bookmarks[0].obj->SetNewFor<CPDF_Reference>("First", m_pIndirectObjs.Get(), | |
| bookmarks[1].num); | |
| bookmarks[0].obj->SetNewFor<CPDF_Reference>("Last", m_pIndirectObjs.Get(), | |
| bookmarks[2].num); | |
| m_pRootObj->SetNewFor<CPDF_Reference>("Outlines", m_pIndirectObjs.Get(), | |
| bookmarks[0].num); | |
| // Title with no match. | |
| std::unique_ptr<unsigned short, pdfium::FreeDeleter> title = | |
| GetFPDFWideString(L"Chapter 8"); | |
| EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| // Title with a match. | |
| title = GetFPDFWideString(L"Chapter 3"); | |
| EXPECT_EQ(FPDFBookmarkFromCPDFDictionary(bookmarks[3].obj), | |
| FPDFBookmark_Find(m_pDoc.get(), title.get())); | |
| } | |
| } | |
| TEST_F(PDFDocTest, GetLocationInPage) { | |
| auto array = pdfium::MakeUnique<CPDF_Array>(); | |
| array->AddNew<CPDF_Number>(0); // Page Index. | |
| array->AddNew<CPDF_Name>("XYZ"); | |
| array->AddNew<CPDF_Number>(4); // X | |
| array->AddNew<CPDF_Number>(5); // Y | |
| array->AddNew<CPDF_Number>(6); // Zoom. | |
| FPDF_BOOL hasX; | |
| FPDF_BOOL hasY; | |
| FPDF_BOOL hasZoom; | |
| FS_FLOAT x; | |
| FS_FLOAT y; | |
| FS_FLOAT zoom; | |
| EXPECT_TRUE(FPDFDest_GetLocationInPage(FPDFDestFromCPDFArray(array.get()), | |
| &hasX, &hasY, &hasZoom, &x, &y, | |
| &zoom)); | |
| EXPECT_TRUE(hasX); | |
| EXPECT_TRUE(hasY); | |
| EXPECT_TRUE(hasZoom); | |
| EXPECT_EQ(4, x); | |
| EXPECT_EQ(5, y); | |
| EXPECT_EQ(6, zoom); | |
| array->SetNewAt<CPDF_Null>(2); | |
| array->SetNewAt<CPDF_Null>(3); | |
| array->SetNewAt<CPDF_Null>(4); | |
| EXPECT_TRUE(FPDFDest_GetLocationInPage(FPDFDestFromCPDFArray(array.get()), | |
| &hasX, &hasY, &hasZoom, &x, &y, | |
| &zoom)); | |
| EXPECT_FALSE(hasX); | |
| EXPECT_FALSE(hasY); | |
| EXPECT_FALSE(hasZoom); | |
| array = pdfium::MakeUnique<CPDF_Array>(); | |
| EXPECT_FALSE(FPDFDest_GetLocationInPage(FPDFDestFromCPDFArray(array.get()), | |
| &hasX, &hasY, &hasZoom, &x, &y, | |
| &zoom)); | |
| } |