// Copyright 2014 The PDFium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com #include "public/fpdf_ppo.h" #include #include #include #include #include #include #include #include "constants/page_object.h" #include "core/fpdfapi/page/cpdf_form.h" #include "core/fpdfapi/page/cpdf_formobject.h" #include "core/fpdfapi/page/cpdf_page.h" #include "core/fpdfapi/page/cpdf_pageimagecache.h" #include "core/fpdfapi/page/cpdf_pageobject.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_document.h" #include "core/fpdfapi/parser/cpdf_name.h" #include "core/fpdfapi/parser/cpdf_number.h" #include "core/fpdfapi/parser/cpdf_object.h" #include "core/fpdfapi/parser/cpdf_reference.h" #include "core/fpdfapi/parser/cpdf_stream.h" #include "core/fpdfapi/parser/cpdf_stream_acc.h" #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fpdfapi/parser/fpdf_parser_utility.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/fx_string_wrappers.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxcrt/unowned_ptr.h" #include "fpdfsdk/cpdfsdk_helpers.h" #include "public/cpp/fpdf_scopers.h" #include "third_party/base/check.h" #include "third_party/base/containers/span.h" struct XObjectContext { UnownedPtr dest_doc; RetainPtr xobject; }; namespace { // Struct that stores sub page origin and scale information. When importing // more than one pages onto the same page, most likely the pages will need to be // scaled down, and scale is in range of (0, 1) exclusive. struct NupPageSettings { CFX_PointF subPageStartPoint; float scale = 0.0f; }; // Calculates the N-up parameters. When importing multiple pages into one page. // The space of output page is evenly divided along the X axis and Y axis based // on the input |nPagesOnXAxis| and |nPagesOnYAxis|. class NupState { public: NupState(const CFX_SizeF& pagesize, size_t nPagesOnXAxis, size_t nPagesOnYAxis); // Calculate sub page origin and scale with the source page of |pagesize| and // new page of |m_subPageSize|. NupPageSettings CalculateNewPagePosition(const CFX_SizeF& pagesize); private: // Helper function to get the |iSubX|, |iSubY| pair based on |m_subPageIndex|. // The space of output page is evenly divided into slots along x and y axis. // |iSubX| and |iSubY| are 0-based indices that indicate which allocation // slot to use. std::pair ConvertPageOrder() const; // Given the |iSubX| and |iSubY| subpage position within a page, and a source // page with dimensions of |pagesize|, calculate the sub page's origin and // scale. NupPageSettings CalculatePageEdit(size_t iSubX, size_t iSubY, const CFX_SizeF& pagesize) const; const CFX_SizeF m_destPageSize; const size_t m_nPagesOnXAxis; const size_t m_nPagesOnYAxis; const size_t m_nPagesPerSheet; CFX_SizeF m_subPageSize; // A 0-based index, in range of [0, m_nPagesPerSheet - 1). size_t m_subPageIndex = 0; }; NupState::NupState(const CFX_SizeF& pagesize, size_t nPagesOnXAxis, size_t nPagesOnYAxis) : m_destPageSize(pagesize), m_nPagesOnXAxis(nPagesOnXAxis), m_nPagesOnYAxis(nPagesOnYAxis), m_nPagesPerSheet(nPagesOnXAxis * nPagesOnYAxis) { DCHECK(m_nPagesOnXAxis > 0); DCHECK(m_nPagesOnYAxis > 0); DCHECK(m_destPageSize.width > 0); DCHECK(m_destPageSize.height > 0); m_subPageSize.width = m_destPageSize.width / m_nPagesOnXAxis; m_subPageSize.height = m_destPageSize.height / m_nPagesOnYAxis; } std::pair NupState::ConvertPageOrder() const { size_t iSubX = m_subPageIndex % m_nPagesOnXAxis; size_t iSubY = m_subPageIndex / m_nPagesOnXAxis; // Y Axis, pages start from the top of the output page. iSubY = m_nPagesOnYAxis - iSubY - 1; return {iSubX, iSubY}; } NupPageSettings NupState::CalculatePageEdit(size_t iSubX, size_t iSubY, const CFX_SizeF& pagesize) const { NupPageSettings settings; settings.subPageStartPoint.x = iSubX * m_subPageSize.width; settings.subPageStartPoint.y = iSubY * m_subPageSize.height; const float xScale = m_subPageSize.width / pagesize.width; const float yScale = m_subPageSize.height / pagesize.height; settings.scale = std::min(xScale, yScale); float subWidth = pagesize.width * settings.scale; float subHeight = pagesize.height * settings.scale; if (xScale > yScale) settings.subPageStartPoint.x += (m_subPageSize.width - subWidth) / 2; else settings.subPageStartPoint.y += (m_subPageSize.height - subHeight) / 2; return settings; } NupPageSettings NupState::CalculateNewPagePosition(const CFX_SizeF& pagesize) { if (m_subPageIndex >= m_nPagesPerSheet) m_subPageIndex = 0; size_t iSubX; size_t iSubY; std::tie(iSubX, iSubY) = ConvertPageOrder(); ++m_subPageIndex; return CalculatePageEdit(iSubX, iSubY, pagesize); } RetainPtr PageDictGetInheritableTag( RetainPtr pDict, const ByteString& bsSrcTag) { if (!pDict || bsSrcTag.IsEmpty()) return nullptr; if (!pDict->KeyExist(pdfium::page_object::kParent) || !pDict->KeyExist(pdfium::page_object::kType)) { return nullptr; } RetainPtr pName = ToName(pDict->GetObjectFor(pdfium::page_object::kType)->GetDirect()); if (!pName || pName->GetString() != "Page") return nullptr; RetainPtr pp = ToDictionary( pDict->GetObjectFor(pdfium::page_object::kParent)->GetDirect()); if (!pp) return nullptr; if (pDict->KeyExist(bsSrcTag)) return pDict->GetObjectFor(bsSrcTag); while (pp) { if (pp->KeyExist(bsSrcTag)) return pp->GetObjectFor(bsSrcTag); if (!pp->KeyExist(pdfium::page_object::kParent)) break; pp = ToDictionary( pp->GetObjectFor(pdfium::page_object::kParent)->GetDirect()); } return nullptr; } bool CopyInheritable(RetainPtr pDestPageDict, RetainPtr pSrcPageDict, const ByteString& key) { if (pDestPageDict->KeyExist(key)) return true; RetainPtr pInheritable = PageDictGetInheritableTag(std::move(pSrcPageDict), key); if (!pInheritable) return false; pDestPageDict->SetFor(key, pInheritable->Clone()); return true; } std::vector GetPageIndices(const CPDF_Document& doc, const ByteString& bsPageRange) { uint32_t nCount = doc.GetPageCount(); if (!bsPageRange.IsEmpty()) return ParsePageRangeString(bsPageRange, nCount); std::vector page_indices(nCount); std::iota(page_indices.begin(), page_indices.end(), 0); return page_indices; } class CPDF_PageOrganizer { protected: CPDF_PageOrganizer(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc); ~CPDF_PageOrganizer(); // Must be called after construction before doing anything else. bool Init(); bool UpdateReference(RetainPtr pObj); CPDF_Document* dest() { return m_pDestDoc; } const CPDF_Document* dest() const { return m_pDestDoc; } CPDF_Document* src() { return m_pSrcDoc; } const CPDF_Document* src() const { return m_pSrcDoc; } void AddObjectMapping(uint32_t dwOldPageObj, uint32_t dwNewPageObj) { m_ObjectNumberMap[dwOldPageObj] = dwNewPageObj; } void ClearObjectNumberMap() { m_ObjectNumberMap.clear(); } private: uint32_t GetNewObjId(CPDF_Reference* pRef); UnownedPtr const m_pDestDoc; UnownedPtr const m_pSrcDoc; // Mapping of source object number to destination object number. std::map m_ObjectNumberMap; }; CPDF_PageOrganizer::CPDF_PageOrganizer(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc) : m_pDestDoc(pDestDoc), m_pSrcDoc(pSrcDoc) {} CPDF_PageOrganizer::~CPDF_PageOrganizer() = default; bool CPDF_PageOrganizer::Init() { DCHECK(m_pDestDoc); DCHECK(m_pSrcDoc); RetainPtr pNewRoot = dest()->GetMutableRoot(); if (!pNewRoot) return false; RetainPtr pDocInfoDict = dest()->GetInfo(); if (!pDocInfoDict) return false; pDocInfoDict->SetNewFor("Producer", "PDFium", false); ByteString cbRootType = pNewRoot->GetByteStringFor("Type", ByteString()); if (cbRootType.IsEmpty()) pNewRoot->SetNewFor("Type", "Catalog"); RetainPtr pElement = pNewRoot->GetMutableObjectFor("Pages"); RetainPtr pNewPages = pElement ? ToDictionary(pElement->GetMutableDirect()) : nullptr; if (!pNewPages) { pNewPages = dest()->NewIndirect(); pNewRoot->SetNewFor("Pages", dest(), pNewPages->GetObjNum()); } ByteString cbPageType = pNewPages->GetByteStringFor("Type", ByteString()); if (cbPageType.IsEmpty()) pNewPages->SetNewFor("Type", "Pages"); if (!pNewPages->GetArrayFor("Kids")) { auto pNewArray = dest()->NewIndirect(); pNewPages->SetNewFor("Count", 0); pNewPages->SetNewFor("Kids", dest(), pNewArray->GetObjNum()); } return true; } bool CPDF_PageOrganizer::UpdateReference(RetainPtr pObj) { switch (pObj->GetType()) { case CPDF_Object::kReference: { CPDF_Reference* pReference = pObj->AsMutableReference(); uint32_t newobjnum = GetNewObjId(pReference); if (newobjnum == 0) return false; pReference->SetRef(dest(), newobjnum); return true; } case CPDF_Object::kDictionary: { CPDF_Dictionary* pDict = pObj->AsMutableDictionary(); std::vector bad_keys; { CPDF_DictionaryLocker locker(pDict); for (const auto& it : locker) { const ByteString& key = it.first; if (key == "Parent" || key == "Prev" || key == "First") continue; RetainPtr pNextObj = it.second; if (!UpdateReference(pNextObj)) bad_keys.push_back(key); } } for (const auto& key : bad_keys) pDict->RemoveFor(key.AsStringView()); return true; } case CPDF_Object::kArray: { CPDF_Array* pArray = pObj->AsMutableArray(); for (size_t i = 0; i < pArray->size(); ++i) { if (!UpdateReference(pArray->GetMutableObjectAt(i))) return false; } return true; } case CPDF_Object::kStream: { CPDF_Stream* pStream = pObj->AsMutableStream(); RetainPtr pDict = pStream->GetMutableDict(); return pDict && UpdateReference(std::move(pDict)); } default: return true; } } uint32_t CPDF_PageOrganizer::GetNewObjId(CPDF_Reference* pRef) { if (!pRef) return 0; uint32_t dwObjnum = pRef->GetRefObjNum(); uint32_t dwNewObjNum = 0; const auto it = m_ObjectNumberMap.find(dwObjnum); if (it != m_ObjectNumberMap.end()) dwNewObjNum = it->second; if (dwNewObjNum) return dwNewObjNum; RetainPtr pDirect = pRef->GetDirect(); if (!pDirect) return 0; RetainPtr pClone = pDirect->Clone(); const CPDF_Dictionary* pDictClone = pClone->AsDictionary(); if (pDictClone && pDictClone->KeyExist("Type")) { ByteString strType = pDictClone->GetByteStringFor("Type"); if (strType.EqualNoCase("Pages")) return 4; if (strType.EqualNoCase("Page")) return 0; } dwNewObjNum = dest()->AddIndirectObject(pClone); AddObjectMapping(dwObjnum, dwNewObjNum); if (!UpdateReference(std::move(pClone))) return 0; return dwNewObjNum; } // Copies pages from a source document into a destination document. // This class is intended to be used once via ExportPage() and then destroyed. class CPDF_PageExporter final : public CPDF_PageOrganizer { public: CPDF_PageExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc); ~CPDF_PageExporter(); // For the pages from the source document with |pageIndices| as their page // indices, insert them into the destination document at page |nIndex|. // |pageIndices| and |nIndex| are 0-based. bool ExportPage(pdfium::span pageIndices, int nIndex); }; CPDF_PageExporter::CPDF_PageExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc) : CPDF_PageOrganizer(pDestDoc, pSrcDoc) {} CPDF_PageExporter::~CPDF_PageExporter() = default; bool CPDF_PageExporter::ExportPage(pdfium::span pageIndices, int nIndex) { if (!Init()) return false; int curpage = nIndex; for (uint32_t pageIndex : pageIndices) { RetainPtr pDestPageDict = dest()->CreateNewPage(curpage); RetainPtr pSrcPageDict = src()->GetPageDictionary(pageIndex); if (!pSrcPageDict || !pDestPageDict) return false; // Clone the page dictionary CPDF_DictionaryLocker locker(pSrcPageDict); for (const auto& it : locker) { const ByteString& cbSrcKeyStr = it.first; const RetainPtr& pObj = it.second; if (cbSrcKeyStr == pdfium::page_object::kType || cbSrcKeyStr == pdfium::page_object::kParent) { continue; } pDestPageDict->SetFor(cbSrcKeyStr, pObj->Clone()); } // inheritable item // Even though some entries are required by the PDF spec, there exist // PDFs that omit them. Set some defaults in this case. // 1 MediaBox - required if (!CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kMediaBox)) { // Search for "CropBox" in the source page dictionary. // If it does not exist, use the default letter size. RetainPtr pInheritable = PageDictGetInheritableTag( pSrcPageDict, pdfium::page_object::kCropBox); if (pInheritable) { pDestPageDict->SetFor(pdfium::page_object::kMediaBox, pInheritable->Clone()); } else { // Make the default size letter size (8.5"x11") static const CFX_FloatRect kDefaultLetterRect(0, 0, 612, 792); pDestPageDict->SetRectFor(pdfium::page_object::kMediaBox, kDefaultLetterRect); } } // 2 Resources - required if (!CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kResources)) { // Use a default empty resources if it does not exist. pDestPageDict->SetNewFor( pdfium::page_object::kResources); } // 3 CropBox - optional CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kCropBox); // 4 Rotate - optional CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kRotate); // Update the reference uint32_t dwOldPageObj = pSrcPageDict->GetObjNum(); uint32_t dwNewPageObj = pDestPageDict->GetObjNum(); AddObjectMapping(dwOldPageObj, dwNewPageObj); UpdateReference(pDestPageDict); ++curpage; } return true; } // Copies pages from a source document into a destination document. Creates 1 // page in the destination document for every N source pages. This class is // intended to be used once via ExportNPagesToOne() and then destroyed. class CPDF_NPageToOneExporter final : public CPDF_PageOrganizer { public: CPDF_NPageToOneExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc); ~CPDF_NPageToOneExporter(); // For the pages from the source document with |pageIndices| as their page // indices, insert them into the destination document, starting at page index // 0. // |pageIndices| is 0-based. // |destPageSize| is the destination document page dimensions, measured in // PDF "user space" units. // |nPagesOnXAxis| and |nPagesOnXAxis| together defines how many source // pages fit on one destination page. bool ExportNPagesToOne(pdfium::span pageIndices, const CFX_SizeF& destPageSize, size_t nPagesOnXAxis, size_t nPagesOnYAxis); std::unique_ptr CreateXObjectContextFromPage( int src_page_index); private: // Map page object number to XObject object name. using PageXObjectMap = std::map; // Creates an XObject from |pSrcPage|, or find an existing XObject that // represents |pSrcPage|. The transformation matrix is specified in // |settings|. // Returns the XObject reference surrounded by the transformation matrix. ByteString AddSubPage(const RetainPtr& pSrcPage, const NupPageSettings& settings); // Creates an XObject from |pSrcPage|. Updates mapping as needed. // Returns the name of the newly created XObject. ByteString MakeXObjectFromPage(RetainPtr pSrcPage); RetainPtr MakeXObjectFromPageRaw(RetainPtr pSrcPage); // Adds |bsContent| as the Contents key in |pDestPageDict|. // Adds the objects in |m_XObjectNameToNumberMap| to the XObject dictionary in // |pDestPageDict|'s Resources dictionary. void FinishPage(RetainPtr pDestPageDict, const ByteString& bsContent); // Counter for giving new XObjects unique names. uint32_t m_nObjectNumber = 0; // Keeps track of created XObjects in the current page. // Map XObject's object name to it's object number. std::map m_XObjectNameToNumberMap; // Mapping of source page object number and XObject name of the entire doc. // If there are multiple source pages that reference the same object number, // they can also share the same created XObject. PageXObjectMap m_SrcPageXObjectMap; }; CPDF_NPageToOneExporter::CPDF_NPageToOneExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc) : CPDF_PageOrganizer(pDestDoc, pSrcDoc) {} CPDF_NPageToOneExporter::~CPDF_NPageToOneExporter() = default; bool CPDF_NPageToOneExporter::ExportNPagesToOne( pdfium::span pageIndices, const CFX_SizeF& destPageSize, size_t nPagesOnXAxis, size_t nPagesOnYAxis) { if (!Init()) return false; FX_SAFE_SIZE_T nSafePagesPerSheet = nPagesOnXAxis; nSafePagesPerSheet *= nPagesOnYAxis; if (!nSafePagesPerSheet.IsValid()) return false; ClearObjectNumberMap(); m_SrcPageXObjectMap.clear(); size_t nPagesPerSheet = nSafePagesPerSheet.ValueOrDie(); NupState nupState(destPageSize, nPagesOnXAxis, nPagesOnYAxis); FX_SAFE_INT32 curpage = 0; const CFX_FloatRect destPageRect(0, 0, destPageSize.width, destPageSize.height); for (size_t iOuterPage = 0; iOuterPage < pageIndices.size(); iOuterPage += nPagesPerSheet) { m_XObjectNameToNumberMap.clear(); RetainPtr pDestPageDict = dest()->CreateNewPage(curpage.ValueOrDie()); if (!pDestPageDict) return false; pDestPageDict->SetRectFor(pdfium::page_object::kMediaBox, destPageRect); ByteString bsContent; size_t iInnerPageMax = std::min(iOuterPage + nPagesPerSheet, pageIndices.size()); for (size_t i = iOuterPage; i < iInnerPageMax; ++i) { RetainPtr pSrcPageDict = src()->GetMutablePageDictionary(pageIndices[i]); if (!pSrcPageDict) return false; auto pSrcPage = pdfium::MakeRetain(src(), pSrcPageDict); pSrcPage->AddPageImageCache(); NupPageSettings settings = nupState.CalculateNewPagePosition(pSrcPage->GetPageSize()); bsContent += AddSubPage(pSrcPage, settings); } FinishPage(pDestPageDict, bsContent); ++curpage; } return true; } ByteString CPDF_NPageToOneExporter::AddSubPage( const RetainPtr& pSrcPage, const NupPageSettings& settings) { uint32_t dwSrcPageObjnum = pSrcPage->GetDict()->GetObjNum(); const auto it = m_SrcPageXObjectMap.find(dwSrcPageObjnum); ByteString bsXObjectName = it != m_SrcPageXObjectMap.end() ? it->second : MakeXObjectFromPage(pSrcPage); CFX_Matrix matrix; matrix.Scale(settings.scale, settings.scale); matrix.Translate(settings.subPageStartPoint.x, settings.subPageStartPoint.y); fxcrt::ostringstream contentStream; contentStream << "q\n" << matrix.a << " " << matrix.b << " " << matrix.c << " " << matrix.d << " " << matrix.e << " " << matrix.f << " cm\n" << "/" << bsXObjectName << " Do Q\n"; return ByteString(contentStream); } RetainPtr CPDF_NPageToOneExporter::MakeXObjectFromPageRaw( RetainPtr pSrcPage) { RetainPtr pSrcPageDict = pSrcPage->GetDict(); RetainPtr pSrcContentObj = pSrcPageDict->GetDirectObjectFor(pdfium::page_object::kContents); auto pNewXObject = dest()->NewIndirect(dest()->New()); RetainPtr pNewXObjectDict = pNewXObject->GetMutableDict(); static const char kResourceString[] = "Resources"; if (!CopyInheritable(pNewXObjectDict, pSrcPageDict, kResourceString)) { // Use a default empty resources if it does not exist. pNewXObjectDict->SetNewFor(kResourceString); } uint32_t dwSrcPageObj = pSrcPageDict->GetObjNum(); uint32_t dwNewXobjectObj = pNewXObjectDict->GetObjNum(); AddObjectMapping(dwSrcPageObj, dwNewXobjectObj); UpdateReference(pNewXObjectDict); pNewXObjectDict->SetNewFor("Type", "XObject"); pNewXObjectDict->SetNewFor("Subtype", "Form"); pNewXObjectDict->SetNewFor("FormType", 1); pNewXObjectDict->SetRectFor("BBox", pSrcPage->GetBBox()); pNewXObjectDict->SetMatrixFor("Matrix", pSrcPage->GetPageMatrix()); if (pSrcContentObj) { ByteString bsSrcContentStream; const CPDF_Array* pSrcContentArray = pSrcContentObj->AsArray(); if (pSrcContentArray) { for (size_t i = 0; i < pSrcContentArray->size(); ++i) { RetainPtr pStream = pSrcContentArray->GetStreamAt(i); auto pAcc = pdfium::MakeRetain(std::move(pStream)); pAcc->LoadAllDataFiltered(); bsSrcContentStream += ByteString(pAcc->GetSpan()); bsSrcContentStream += "\n"; } } else { RetainPtr pStream(pSrcContentObj->AsStream()); auto pAcc = pdfium::MakeRetain(std::move(pStream)); pAcc->LoadAllDataFiltered(); bsSrcContentStream = ByteString(pAcc->GetSpan()); } pNewXObject->SetDataAndRemoveFilter(bsSrcContentStream.raw_span()); } return pNewXObject; } ByteString CPDF_NPageToOneExporter::MakeXObjectFromPage( RetainPtr pSrcPage) { RetainPtr pNewXObject = MakeXObjectFromPageRaw(pSrcPage); // TODO(xlou): A better name schema to avoid possible object name collision. ByteString bsXObjectName = ByteString::Format("X%d", ++m_nObjectNumber); m_XObjectNameToNumberMap[bsXObjectName] = pNewXObject->GetObjNum(); m_SrcPageXObjectMap[pSrcPage->GetDict()->GetObjNum()] = bsXObjectName; return bsXObjectName; } std::unique_ptr CPDF_NPageToOneExporter::CreateXObjectContextFromPage(int src_page_index) { RetainPtr src_page_dict = src()->GetMutablePageDictionary(src_page_index); if (!src_page_dict) return nullptr; auto src_page = pdfium::MakeRetain(src(), src_page_dict); auto xobject = std::make_unique(); xobject->dest_doc = dest(); xobject->xobject.Reset(MakeXObjectFromPageRaw(src_page)); return xobject; } void CPDF_NPageToOneExporter::FinishPage( RetainPtr pDestPageDict, const ByteString& bsContent) { RetainPtr pRes = pDestPageDict->GetOrCreateDictFor(pdfium::page_object::kResources); RetainPtr pPageXObject = pRes->GetOrCreateDictFor("XObject"); for (auto& it : m_XObjectNameToNumberMap) pPageXObject->SetNewFor(it.first, dest(), it.second); auto pStream = dest()->NewIndirect(dest()->New()); pStream->SetData(bsContent.raw_span()); pDestPageDict->SetNewFor(pdfium::page_object::kContents, dest(), pStream->GetObjNum()); } // Make sure arrays only contain objects of basic types. bool IsValidViewerPreferencesArray(const CPDF_Array* array) { CPDF_ArrayLocker locker(array); for (const auto& obj : locker) { if (obj->IsArray() || obj->IsDictionary() || obj->IsReference() || obj->IsStream()) { return false; } } return true; } bool IsValidViewerPreferencesObject(const CPDF_Object* obj) { // Per spec, there are no valid entries of these types. if (obj->IsDictionary() || obj->IsNull() || obj->IsReference() || obj->IsStream()) { return false; } const CPDF_Array* array = obj->AsArray(); if (!array) { return true; } return IsValidViewerPreferencesArray(array); } } // namespace FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPagesByIndex(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc, const int* page_indices, unsigned long length, int index) { CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(dest_doc); if (!dest_doc) return false; CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc); if (!pSrcDoc) return false; CPDF_PageExporter exporter(pDestDoc, pSrcDoc); if (!page_indices) { std::vector page_indices_vec(pSrcDoc->GetPageCount()); std::iota(page_indices_vec.begin(), page_indices_vec.end(), 0); return exporter.ExportPage(page_indices_vec, index); } if (length == 0) return false; return exporter.ExportPage( pdfium::make_span(reinterpret_cast(page_indices), length), index); } FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc, FPDF_BYTESTRING pagerange, int index) { CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(dest_doc); if (!dest_doc) return false; CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc); if (!pSrcDoc) return false; std::vector page_indices = GetPageIndices(*pSrcDoc, pagerange); if (page_indices.empty()) return false; CPDF_PageExporter exporter(pDestDoc, pSrcDoc); return exporter.ExportPage(page_indices, index); } FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc, float output_width, float output_height, size_t num_pages_on_x_axis, size_t num_pages_on_y_axis) { CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc); if (!pSrcDoc) return nullptr; if (output_width <= 0 || output_height <= 0 || num_pages_on_x_axis <= 0 || num_pages_on_y_axis <= 0) { return nullptr; } ScopedFPDFDocument output_doc(FPDF_CreateNewDocument()); if (!output_doc) return nullptr; CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(output_doc.get()); DCHECK(pDestDoc); std::vector page_indices = GetPageIndices(*pSrcDoc, ByteString()); if (page_indices.empty()) return nullptr; if (num_pages_on_x_axis == 1 && num_pages_on_y_axis == 1) { CPDF_PageExporter exporter(pDestDoc, pSrcDoc); if (!exporter.ExportPage(page_indices, 0)) return nullptr; return output_doc.release(); } CPDF_NPageToOneExporter exporter(pDestDoc, pSrcDoc); if (!exporter.ExportNPagesToOne(page_indices, CFX_SizeF(output_width, output_height), num_pages_on_x_axis, num_pages_on_y_axis)) { return nullptr; } return output_doc.release(); } FPDF_EXPORT FPDF_XOBJECT FPDF_CALLCONV FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc, int src_page_index) { CPDF_Document* dest = CPDFDocumentFromFPDFDocument(dest_doc); if (!dest) return nullptr; CPDF_Document* src = CPDFDocumentFromFPDFDocument(src_doc); if (!src) return nullptr; CPDF_NPageToOneExporter exporter(dest, src); std::unique_ptr xobject = exporter.CreateXObjectContextFromPage(src_page_index); return FPDFXObjectFromXObjectContext(xobject.release()); } FPDF_EXPORT void FPDF_CALLCONV FPDF_CloseXObject(FPDF_XOBJECT xobject) { std::unique_ptr xobject_deleter( XObjectContextFromFPDFXObject(xobject)); } FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject) { XObjectContext* xobj = XObjectContextFromFPDFXObject(xobject); if (!xobj) return nullptr; auto form = std::make_unique(xobj->dest_doc, nullptr, xobj->xobject, nullptr); form->ParseContent(nullptr, nullptr, nullptr); auto form_object = std::make_unique( CPDF_PageObject::kNoContentStream, std::move(form), CFX_Matrix()); return FPDFPageObjectFromCPDFPageObject(form_object.release()); } FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc) { CPDF_Document* pDstDoc = CPDFDocumentFromFPDFDocument(dest_doc); if (!pDstDoc) return false; CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc); if (!pSrcDoc) return false; RetainPtr pPrefDict = pSrcDoc->GetRoot()->GetDictFor("ViewerPreferences"); if (!pPrefDict) return false; RetainPtr pDstDict = pDstDoc->GetMutableRoot(); if (!pDstDict) return false; auto cloned_dict = pdfium::MakeRetain(); CPDF_DictionaryLocker locker(pPrefDict); for (const auto& it : locker) { if (IsValidViewerPreferencesObject(it.second)) { cloned_dict->SetFor(it.first, it.second->Clone()); } } pDstDict->SetFor("ViewerPreferences", std::move(cloned_dict)); return true; }