| // 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 <algorithm> | 
 | #include <map> | 
 | #include <memory> | 
 | #include <numeric> | 
 | #include <sstream> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #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/check.h" | 
 | #include "core/fxcrt/fx_safe_types.h" | 
 | #include "core/fxcrt/fx_string_wrappers.h" | 
 | #include "core/fxcrt/retain_ptr.h" | 
 | #include "core/fxcrt/span.h" | 
 | #include "core/fxcrt/unowned_ptr.h" | 
 | #include "fpdfsdk/cpdfsdk_helpers.h" | 
 | #include "public/cpp/fpdf_scopers.h" | 
 |  | 
 | struct XObjectContext { | 
 |   UnownedPtr<CPDF_Document> dest_doc; | 
 |   RetainPtr<CPDF_Stream> 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<size_t, size_t> 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<size_t, size_t> 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<const CPDF_Object> PageDictGetInheritableTag( | 
 |     RetainPtr<const CPDF_Dictionary> 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<const CPDF_Name> pName = | 
 |       ToName(pDict->GetObjectFor(pdfium::page_object::kType)->GetDirect()); | 
 |   if (!pName || pName->GetString() != "Page") | 
 |     return nullptr; | 
 |  | 
 |   RetainPtr<const CPDF_Dictionary> 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<CPDF_Dictionary> pDestPageDict, | 
 |                      RetainPtr<const CPDF_Dictionary> pSrcPageDict, | 
 |                      const ByteString& key) { | 
 |   if (pDestPageDict->KeyExist(key)) | 
 |     return true; | 
 |  | 
 |   RetainPtr<const CPDF_Object> pInheritable = | 
 |       PageDictGetInheritableTag(std::move(pSrcPageDict), key); | 
 |   if (!pInheritable) | 
 |     return false; | 
 |  | 
 |   pDestPageDict->SetFor(key, pInheritable->Clone()); | 
 |   return true; | 
 | } | 
 |  | 
 | std::vector<uint32_t> GetPageIndices(const CPDF_Document& doc, | 
 |                                      const ByteString& bsPageRange) { | 
 |   uint32_t nCount = doc.GetPageCount(); | 
 |   if (!bsPageRange.IsEmpty()) | 
 |     return ParsePageRangeString(bsPageRange, nCount); | 
 |  | 
 |   std::vector<uint32_t> 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<CPDF_Object> 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: | 
 |   bool InitDestDoc(); | 
 |  | 
 |   uint32_t GetNewObjId(CPDF_Reference* pRef); | 
 |  | 
 |   UnownedPtr<CPDF_Document> const m_pDestDoc; | 
 |   UnownedPtr<CPDF_Document> const m_pSrcDoc; | 
 |  | 
 |   // Mapping of source object number to destination object number. | 
 |   std::map<uint32_t, uint32_t> 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); | 
 |  | 
 |   return InitDestDoc(); | 
 | } | 
 |  | 
 | bool CPDF_PageOrganizer::InitDestDoc() { | 
 |   RetainPtr<CPDF_Dictionary> root = dest()->GetMutableRoot(); | 
 |   if (!root) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   RetainPtr<CPDF_Dictionary> info = dest()->GetInfo(); | 
 |   if (info) { | 
 |     info->SetNewFor<CPDF_String>("Producer", "PDFium"); | 
 |   } | 
 |  | 
 |   if (root->GetByteStringFor("Type", ByteString()).IsEmpty()) { | 
 |     root->SetNewFor<CPDF_Name>("Type", "Catalog"); | 
 |   } | 
 |  | 
 |   RetainPtr<CPDF_Dictionary> pages; | 
 |   if (RetainPtr<CPDF_Object> current_pages = root->GetMutableObjectFor("Pages"); | 
 |       current_pages) { | 
 |     pages = ToDictionary(current_pages->GetMutableDirect()); | 
 |   } | 
 |   if (!pages) { | 
 |     pages = dest()->NewIndirect<CPDF_Dictionary>(); | 
 |     root->SetNewFor<CPDF_Reference>("Pages", dest(), pages->GetObjNum()); | 
 |   } | 
 |   if (pages->GetByteStringFor("Type", ByteString()).IsEmpty()) { | 
 |     pages->SetNewFor<CPDF_Name>("Type", "Pages"); | 
 |   } | 
 |  | 
 |   if (!pages->GetArrayFor("Kids")) { | 
 |     auto kids_array = dest()->NewIndirect<CPDF_Array>(); | 
 |     pages->SetNewFor<CPDF_Number>("Count", 0); | 
 |     pages->SetNewFor<CPDF_Reference>("Kids", dest(), kids_array->GetObjNum()); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | bool CPDF_PageOrganizer::UpdateReference(RetainPtr<CPDF_Object> 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<ByteString> 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<CPDF_Object> 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: { | 
 |       return UpdateReference(pObj->AsMutableStream()->GetMutableDict()); | 
 |     } | 
 |     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<const CPDF_Object> pDirect = pRef->GetDirect(); | 
 |   if (!pDirect) | 
 |     return 0; | 
 |  | 
 |   RetainPtr<CPDF_Object> 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<const uint32_t> 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<const uint32_t> pageIndices, | 
 |                                    int nIndex) { | 
 |   if (!Init()) | 
 |     return false; | 
 |  | 
 |   int curpage = nIndex; | 
 |   for (uint32_t pageIndex : pageIndices) { | 
 |     RetainPtr<CPDF_Dictionary> pDestPageDict = dest()->CreateNewPage(curpage); | 
 |     RetainPtr<const CPDF_Dictionary> 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<CPDF_Object>& 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<const CPDF_Object> 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<CPDF_Dictionary>( | 
 |           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<const uint32_t> pageIndices, | 
 |                          const CFX_SizeF& destPageSize, | 
 |                          size_t nPagesOnXAxis, | 
 |                          size_t nPagesOnYAxis); | 
 |  | 
 |   std::unique_ptr<XObjectContext> CreateXObjectContextFromPage( | 
 |       int src_page_index); | 
 |  | 
 |  private: | 
 |   // Map page object number to XObject object name. | 
 |   using PageXObjectMap = std::map<uint32_t, ByteString>; | 
 |  | 
 |   // 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<CPDF_Page>& pSrcPage, | 
 |                         const NupPageSettings& settings); | 
 |  | 
 |   // Creates an XObject from |pSrcPage|. Updates mapping as needed. | 
 |   // Returns the name of the newly created XObject. | 
 |   ByteString MakeXObjectFromPage(RetainPtr<CPDF_Page> pSrcPage); | 
 |   RetainPtr<CPDF_Stream> MakeXObjectFromPageRaw(RetainPtr<CPDF_Page> 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<CPDF_Dictionary> 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<ByteString, uint32_t> 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<const uint32_t> 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<CPDF_Dictionary> 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<CPDF_Dictionary> pSrcPageDict = | 
 |           src()->GetMutablePageDictionary(pageIndices[i]); | 
 |       if (!pSrcPageDict) | 
 |         return false; | 
 |  | 
 |       auto pSrcPage = pdfium::MakeRetain<CPDF_Page>(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<CPDF_Page>& 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_Stream> CPDF_NPageToOneExporter::MakeXObjectFromPageRaw( | 
 |     RetainPtr<CPDF_Page> pSrcPage) { | 
 |   RetainPtr<const CPDF_Dictionary> pSrcPageDict = pSrcPage->GetDict(); | 
 |   RetainPtr<const CPDF_Object> pSrcContentObj = | 
 |       pSrcPageDict->GetDirectObjectFor(pdfium::page_object::kContents); | 
 |  | 
 |   auto pNewXObject = | 
 |       dest()->NewIndirect<CPDF_Stream>(dest()->New<CPDF_Dictionary>()); | 
 |   RetainPtr<CPDF_Dictionary> 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<CPDF_Dictionary>(kResourceString); | 
 |   } | 
 |   uint32_t dwSrcPageObj = pSrcPageDict->GetObjNum(); | 
 |   uint32_t dwNewXobjectObj = pNewXObjectDict->GetObjNum(); | 
 |   AddObjectMapping(dwSrcPageObj, dwNewXobjectObj); | 
 |   UpdateReference(pNewXObjectDict); | 
 |   pNewXObjectDict->SetNewFor<CPDF_Name>("Type", "XObject"); | 
 |   pNewXObjectDict->SetNewFor<CPDF_Name>("Subtype", "Form"); | 
 |   pNewXObjectDict->SetNewFor<CPDF_Number>("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<const CPDF_Stream> pStream = pSrcContentArray->GetStreamAt(i); | 
 |         auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(std::move(pStream)); | 
 |         pAcc->LoadAllDataFiltered(); | 
 |         bsSrcContentStream += ByteString(pAcc->GetSpan()); | 
 |         bsSrcContentStream += "\n"; | 
 |       } | 
 |     } else { | 
 |       RetainPtr<const CPDF_Stream> pStream(pSrcContentObj->AsStream()); | 
 |       auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(std::move(pStream)); | 
 |       pAcc->LoadAllDataFiltered(); | 
 |       bsSrcContentStream = ByteString(pAcc->GetSpan()); | 
 |     } | 
 |     pNewXObject->SetDataAndRemoveFilter(bsSrcContentStream.unsigned_span()); | 
 |   } | 
 |   return pNewXObject; | 
 | } | 
 |  | 
 | ByteString CPDF_NPageToOneExporter::MakeXObjectFromPage( | 
 |     RetainPtr<CPDF_Page> pSrcPage) { | 
 |   RetainPtr<CPDF_Stream> 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<XObjectContext> | 
 | CPDF_NPageToOneExporter::CreateXObjectContextFromPage(int src_page_index) { | 
 |   RetainPtr<CPDF_Dictionary> src_page_dict = | 
 |       src()->GetMutablePageDictionary(src_page_index); | 
 |   if (!src_page_dict) | 
 |     return nullptr; | 
 |  | 
 |   auto src_page = pdfium::MakeRetain<CPDF_Page>(src(), src_page_dict); | 
 |   auto xobject = std::make_unique<XObjectContext>(); | 
 |   xobject->dest_doc = dest(); | 
 |   xobject->xobject.Reset(MakeXObjectFromPageRaw(src_page)); | 
 |   return xobject; | 
 | } | 
 |  | 
 | void CPDF_NPageToOneExporter::FinishPage( | 
 |     RetainPtr<CPDF_Dictionary> pDestPageDict, | 
 |     const ByteString& bsContent) { | 
 |   RetainPtr<CPDF_Dictionary> pRes = | 
 |       pDestPageDict->GetOrCreateDictFor(pdfium::page_object::kResources); | 
 |   RetainPtr<CPDF_Dictionary> pPageXObject = pRes->GetOrCreateDictFor("XObject"); | 
 |   for (auto& it : m_XObjectNameToNumberMap) | 
 |     pPageXObject->SetNewFor<CPDF_Reference>(it.first, dest(), it.second); | 
 |  | 
 |   auto pStream = | 
 |       dest()->NewIndirect<CPDF_Stream>(dest()->New<CPDF_Dictionary>()); | 
 |   pStream->SetData(bsContent.unsigned_span()); | 
 |   pDestPageDict->SetNewFor<CPDF_Reference>(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<uint32_t> 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; | 
 |   } | 
 |   auto page_span = UNSAFE_TODO(pdfium::make_span( | 
 |       reinterpret_cast<const uint32_t*>(page_indices), length)); | 
 |   return exporter.ExportPage(page_span, 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<uint32_t> 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<uint32_t> 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<XObjectContext> xobject = | 
 |       exporter.CreateXObjectContextFromPage(src_page_index); | 
 |   return FPDFXObjectFromXObjectContext(xobject.release()); | 
 | } | 
 |  | 
 | FPDF_EXPORT void FPDF_CALLCONV FPDF_CloseXObject(FPDF_XOBJECT xobject) { | 
 |   std::unique_ptr<XObjectContext> 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<CPDF_Form>(xobj->dest_doc, nullptr, | 
 |                                           xobj->xobject, nullptr); | 
 |   form->ParseContent(nullptr, nullptr, nullptr); | 
 |   auto form_object = std::make_unique<CPDF_FormObject>( | 
 |       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<const CPDF_Dictionary> pPrefDict = | 
 |       pSrcDoc->GetRoot()->GetDictFor("ViewerPreferences"); | 
 |   if (!pPrefDict) | 
 |     return false; | 
 |  | 
 |   RetainPtr<CPDF_Dictionary> pDstDict = pDstDoc->GetMutableRoot(); | 
 |   if (!pDstDict) | 
 |     return false; | 
 |  | 
 |   auto cloned_dict = pdfium::MakeRetain<CPDF_Dictionary>(); | 
 |   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; | 
 | } |