Split CPDF_PageOrganizer into multiple classes.

Retain CPDF_PageOrganizer as a base class. Add CPDF_PageExporter and
CPDF_NPageToOneExporter sub-classes. The sub-classes only do one type of
exporting. Therefore it is much easier to understand what part of the
original combined CPDF_PageOrganizer is used where.

Change-Id: I424ef9c32b101d23e8397fc9a656d9b0b5da6a27
Reviewed-on: https://pdfium-review.googlesource.com/26011
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Henrique Nakashima <hnakashima@chromium.org>
Reviewed-by: Shirleen Lou <xlou@chromium.org>
diff --git a/fpdfsdk/fpdf_ppo.cpp b/fpdfsdk/fpdf_ppo.cpp
index f51bb22..efec4b2 100644
--- a/fpdfsdk/fpdf_ppo.cpp
+++ b/fpdfsdk/fpdf_ppo.cpp
@@ -283,50 +283,18 @@
   return page_numbers;
 }
 
-}  // namespace
-
 class CPDF_PageOrganizer {
- public:
+ protected:
+  // Map source page object number to XObject object number.
+  using ObjectNumberMap = std::map<uint32_t, uint32_t>;
+
   CPDF_PageOrganizer(CPDF_Document* pDestPDFDoc, CPDF_Document* pSrcPDFDoc);
   ~CPDF_PageOrganizer();
 
+  // Must be called after construction before doing anything else.
   bool PDFDocInit();
-  bool ExportPage(const std::vector<uint32_t>& pageNums, int nIndex);
-  bool ExportNPagesToOne(const std::vector<uint32_t>& pageNums,
-                         float destPageWidth,
-                         float destPageHeight,
-                         unsigned int numPagesOnXAxis,
-                         unsigned int numPagesOnYAxis);
-
- private:
-  // Map source page object number to XObject object number.
-  using ObjectNumberMap = std::map<uint32_t, uint32_t>;
-  // Map page object number to XObject object name.
-  using PageXObjectMap = std::map<uint32_t, ByteString>;
-  // Map XObject's object name to it's object number.
-  using XObjectNameNumberMap = std::map<ByteString, uint32_t>;
-
-  static void SetMediaBox(CPDF_Dictionary* pDestPageDict,
-                          const CFX_SizeF& pagesize);
 
   bool UpdateReference(CPDF_Object* pObj, ObjectNumberMap* pObjNumberMap);
-  uint32_t GetNewObjId(ObjectNumberMap* pObjNumberMap, CPDF_Reference* pRef);
-  // Creates a xobject from the source page dictionary, and appends the
-  // bsContent string with the xobject reference surrounded by the
-  // transformation matrix.
-  void AddSubPage(CPDF_Dictionary* pPageDict,
-                  const CFX_PointF& position,
-                  float scale,
-                  ObjectNumberMap* pObjNumberMap,
-                  PageXObjectMap* pPageXObjectMap,
-                  XObjectNameNumberMap* pXObjNameNumberMap,
-                  ByteString* bsContent);
-  uint32_t MakeXObject(CPDF_Dictionary* pSrcPageDict,
-                       ObjectNumberMap* pObjNumberMap,
-                       CPDF_Document* pDestDoc);
-  void FinishPage(CPDF_Dictionary* pCurPageDict,
-                  const ByteString& bsContent,
-                  XObjectNameNumberMap* pXObjNameNumberMap);
 
   CPDF_Document* dest() { return m_pDestPDFDoc.Get(); }
   const CPDF_Document* dest() const { return m_pDestPDFDoc.Get(); }
@@ -334,13 +302,11 @@
   CPDF_Document* src() { return m_pSrcPDFDoc.Get(); }
   const CPDF_Document* src() const { return m_pSrcPDFDoc.Get(); }
 
+ private:
+  uint32_t GetNewObjId(ObjectNumberMap* pObjNumberMap, CPDF_Reference* pRef);
+
   UnownedPtr<CPDF_Document> const m_pDestPDFDoc;
   UnownedPtr<CPDF_Document> const m_pSrcPDFDoc;
-  uint32_t m_xobjectNum = 0;
-  CFX_SizeF m_pageSize;
-  // Mapping of XObject name and XObject object number of the entire document.
-  // Key is XObject name.
-  XObjectNameNumberMap m_xobjs;
 };
 
 CPDF_PageOrganizer::CPDF_PageOrganizer(CPDF_Document* pDestPDFDoc,
@@ -389,249 +355,6 @@
   return true;
 }
 
-void CPDF_PageOrganizer::AddSubPage(CPDF_Dictionary* pPageDict,
-                                    const CFX_PointF& position,
-                                    float scale,
-                                    ObjectNumberMap* pObjNumberMap,
-                                    PageXObjectMap* pPageXObjectMap,
-                                    XObjectNameNumberMap* pXObjNameNumberMap,
-                                    ByteString* bsContent) {
-  uint32_t dwPageObjnum = pPageDict->GetObjNum();
-  ByteString bsXObjectName;
-  const auto it = pPageXObjectMap->find(dwPageObjnum);
-  if (it != pPageXObjectMap->end()) {
-    bsXObjectName = it->second;
-  } else {
-    ++m_xobjectNum;
-    // TODO(Xlou): A better name schema to avoid possible object name collision.
-    bsXObjectName = ByteString::Format("X%d", m_xobjectNum);
-    m_xobjs[bsXObjectName] = MakeXObject(pPageDict, pObjNumberMap, dest());
-    (*pPageXObjectMap)[dwPageObjnum] = bsXObjectName;
-  }
-  (*pXObjNameNumberMap)[bsXObjectName] = m_xobjs[bsXObjectName];
-
-  CFX_Matrix matrix;
-  matrix.Scale(scale, scale);
-  matrix.Translate(position.x, position.y);
-
-  std::ostringstream contentStream;
-  contentStream << "q\n"
-                << matrix.a << " " << matrix.b << " " << matrix.c << " "
-                << matrix.d << " " << matrix.e << " " << matrix.f << " cm\n"
-                << "/" << bsXObjectName << " Do Q\n";
-  *bsContent += ByteString(contentStream);
-}
-
-uint32_t CPDF_PageOrganizer::MakeXObject(CPDF_Dictionary* pSrcPageDict,
-                                         ObjectNumberMap* pObjNumberMap,
-                                         CPDF_Document* pDestDoc) {
-  ASSERT(pSrcPageDict);
-
-  CPDF_Object* pSrcContentObj = GetPageOrganizerPageContent(pSrcPageDict);
-  CPDF_Stream* pNewXObject = pDestDoc->NewIndirect<CPDF_Stream>(
-      nullptr, 0,
-      pdfium::MakeUnique<CPDF_Dictionary>(pDestDoc->GetByteStringPool()));
-  CPDF_Dictionary* pNewXObjectDict = pNewXObject->GetDict();
-  const ByteString bsResourceString = "Resources";
-  if (!CopyInheritable(pNewXObjectDict, pSrcPageDict, bsResourceString)) {
-    // Use a default empty resources if it does not exist.
-    pNewXObjectDict->SetNewFor<CPDF_Dictionary>(bsResourceString);
-  }
-  uint32_t dwSrcPageObj = pSrcPageDict->GetObjNum();
-  uint32_t dwNewXobjectObj = pNewXObjectDict->GetObjNum();
-  (*pObjNumberMap)[dwSrcPageObj] = dwNewXobjectObj;
-  UpdateReference(pNewXObjectDict, pObjNumberMap);
-
-  pNewXObjectDict->SetNewFor<CPDF_Name>("Type", "XObject");
-  pNewXObjectDict->SetNewFor<CPDF_Name>("Subtype", "Form");
-  pNewXObjectDict->SetNewFor<CPDF_Number>("FormType", 1);
-  pNewXObjectDict->SetRectFor("BBox", GetTrimBox(pSrcPageDict));
-  // TODO(xlou): add matrix field to pNewXObjectDict.
-
-  if (CPDF_Array* pSrcContentArray = ToArray(pSrcContentObj)) {
-    ByteString bsSrcContentStream;
-    for (size_t i = 0; i < pSrcContentArray->GetCount(); ++i) {
-      CPDF_Stream* pStream = pSrcContentArray->GetStreamAt(i);
-      auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
-      pAcc->LoadAllDataFiltered();
-      ByteString bsStream(pAcc->GetData(), pAcc->GetSize());
-      bsSrcContentStream += bsStream;
-      bsSrcContentStream += "\n";
-    }
-    pNewXObject->SetDataAndRemoveFilter(bsSrcContentStream.raw_str(),
-                                        bsSrcContentStream.GetLength());
-  } else {
-    CPDF_Stream* pStream = pSrcContentObj->AsStream();
-    auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
-    pAcc->LoadAllDataFiltered();
-    ByteString bsStream(pAcc->GetData(), pAcc->GetSize());
-    pNewXObject->SetDataAndRemoveFilter(bsStream.raw_str(),
-                                        bsStream.GetLength());
-  }
-
-  return pNewXObject->GetObjNum();
-}
-
-// static
-void CPDF_PageOrganizer::SetMediaBox(CPDF_Dictionary* pDestPageDict,
-                                     const CFX_SizeF& pagesize) {
-  CPDF_Array* pArray = pDestPageDict->SetNewFor<CPDF_Array>("MediaBox");
-  pArray->AddNew<CPDF_Number>(0);
-  pArray->AddNew<CPDF_Number>(0);
-  pArray->AddNew<CPDF_Number>(pagesize.width);
-  pArray->AddNew<CPDF_Number>(pagesize.height);
-}
-
-bool CPDF_PageOrganizer::ExportPage(const std::vector<uint32_t>& pageNums,
-                                    int nIndex) {
-  int curpage = nIndex;
-  auto pObjNumberMap = pdfium::MakeUnique<ObjectNumberMap>();
-  for (size_t i = 0; i < pageNums.size(); ++i) {
-    CPDF_Dictionary* pCurPageDict = dest()->CreateNewPage(curpage);
-    CPDF_Dictionary* pSrcPageDict = src()->GetPage(pageNums[i] - 1);
-    if (!pSrcPageDict || !pCurPageDict)
-      return false;
-
-    // Clone the page dictionary
-    for (const auto& it : *pSrcPageDict) {
-      const ByteString& cbSrcKeyStr = it.first;
-      if (cbSrcKeyStr == "Type" || cbSrcKeyStr == "Parent")
-        continue;
-
-      CPDF_Object* pObj = it.second.get();
-      pCurPageDict->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(pCurPageDict, pSrcPageDict, "MediaBox")) {
-      // Search for "CropBox" in the source page dictionary.
-      // If it does not exist, use the default letter size.
-      CPDF_Object* pInheritable =
-          PageDictGetInheritableTag(pSrcPageDict, "CropBox");
-      if (pInheritable) {
-        pCurPageDict->SetFor("MediaBox", pInheritable->Clone());
-      } else {
-        // Make the default size letter size (8.5"x11")
-        CPDF_Array* pArray = pCurPageDict->SetNewFor<CPDF_Array>("MediaBox");
-        pArray->AddNew<CPDF_Number>(0);
-        pArray->AddNew<CPDF_Number>(0);
-        pArray->AddNew<CPDF_Number>(612);
-        pArray->AddNew<CPDF_Number>(792);
-      }
-    }
-
-    // 2 Resources - required
-    if (!CopyInheritable(pCurPageDict, pSrcPageDict, "Resources")) {
-      // Use a default empty resources if it does not exist.
-      pCurPageDict->SetNewFor<CPDF_Dictionary>("Resources");
-    }
-
-    // 3 CropBox - optional
-    CopyInheritable(pCurPageDict, pSrcPageDict, "CropBox");
-    // 4 Rotate - optional
-    CopyInheritable(pCurPageDict, pSrcPageDict, "Rotate");
-
-    // Update the reference
-    uint32_t dwOldPageObj = pSrcPageDict->GetObjNum();
-    uint32_t dwNewPageObj = pCurPageDict->GetObjNum();
-    (*pObjNumberMap)[dwOldPageObj] = dwNewPageObj;
-    UpdateReference(pCurPageDict, pObjNumberMap.get());
-    ++curpage;
-  }
-
-  return true;
-}
-
-void CPDF_PageOrganizer::FinishPage(CPDF_Dictionary* pCurPageDict,
-                                    const ByteString& bsContent,
-                                    XObjectNameNumberMap* pXObjNameNumberMap) {
-  ASSERT(pCurPageDict);
-
-  CPDF_Dictionary* pRes = pCurPageDict->GetDictFor("Resources");
-  if (!pRes)
-    pRes = pCurPageDict->SetNewFor<CPDF_Dictionary>("Resources");
-
-  CPDF_Dictionary* pPageXObject = pRes->GetDictFor("XObject");
-  if (!pPageXObject)
-    pPageXObject = pRes->SetNewFor<CPDF_Dictionary>("XObject");
-
-  for (auto& it : *pXObjNameNumberMap) {
-    pPageXObject->SetNewFor<CPDF_Reference>(it.first, dest(), it.second);
-  }
-
-  auto pDict = pdfium::MakeUnique<CPDF_Dictionary>(dest()->GetByteStringPool());
-  CPDF_Stream* pStream =
-      dest()->NewIndirect<CPDF_Stream>(nullptr, 0, std::move(pDict));
-  pStream->SetData(bsContent.raw_str(), bsContent.GetLength());
-  pCurPageDict->SetNewFor<CPDF_Reference>("Contents", dest(),
-                                          pStream->GetObjNum());
-}
-
-bool CPDF_PageOrganizer::ExportNPagesToOne(
-    const std::vector<uint32_t>& pageNums,
-    float destPageWidth,
-    float destPageHeight,
-    unsigned int numPagesOnXAxis,
-    unsigned int numPagesOnYAxis) {
-  FX_SAFE_SIZE_T safe_numPagesPerSheet = numPagesOnXAxis;
-  safe_numPagesPerSheet *= numPagesOnYAxis;
-
-  if (!safe_numPagesPerSheet.IsValid())
-    return false;
-
-  size_t numPagesPerSheet = safe_numPagesPerSheet.ValueOrDie();
-  if (numPagesPerSheet == 1)
-    return ExportPage(pageNums, 0);
-  // Mapping of source page object number and XObject object number.
-  // Used to update refernece.
-  ObjectNumberMap objectNumberMap;
-  // Mapping of source page object number and XObject name of the entire doc.
-  // If there are two pages that are identical and have the same object number,
-  // we can reuse one created XObject.
-  PageXObjectMap pageXObjectMap;
-  const CFX_SizeF pagesize(destPageWidth, destPageHeight);
-  NupState nupState(destPageWidth, destPageHeight, numPagesOnXAxis,
-                    numPagesOnYAxis);
-
-  size_t curpage = 0;
-  for (size_t outerPage = 0; outerPage < pageNums.size();
-       outerPage += numPagesPerSheet) {
-    // Create a new page
-    CPDF_Dictionary* pCurPageDict = dest()->CreateNewPage(curpage);
-    if (!pCurPageDict)
-      return false;
-
-    SetMediaBox(pCurPageDict, pagesize);
-    ByteString bsContent;
-    size_t innerPageMax =
-        std::min(outerPage + numPagesPerSheet, pageNums.size());
-    // Mapping of XObject name and XObject object number of one page.
-    XObjectNameNumberMap xObjectNameNumberMap;
-    for (size_t innerPage = outerPage; innerPage < innerPageMax; ++innerPage) {
-      CPDF_Dictionary* pSrcPageDict = src()->GetPage(pageNums[innerPage] - 1);
-      if (!pSrcPageDict)
-        return false;
-
-      CPDF_Page srcPage(src(), pSrcPageDict, true);
-      NupPageSettings pgEdit;
-      nupState.CalculateNewPagePosition(srcPage.GetPageWidth(),
-                                        srcPage.GetPageHeight(), &pgEdit);
-      AddSubPage(pSrcPageDict, pgEdit.subPageStartPoint, pgEdit.scale,
-                 &objectNumberMap, &pageXObjectMap, &xObjectNameNumberMap,
-                 &bsContent);
-    }
-
-    // Finish up the current page.
-    FinishPage(pCurPageDict, bsContent, &xObjectNameNumberMap);
-    ++curpage;
-  }
-
-  return true;
-}
-
 bool CPDF_PageOrganizer::UpdateReference(CPDF_Object* pObj,
                                          ObjectNumberMap* pObjNumberMap) {
   switch (pObj->GetType()) {
@@ -722,6 +445,337 @@
   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 : public CPDF_PageOrganizer {
+ public:
+  CPDF_PageExporter(CPDF_Document* pDestPDFDoc, CPDF_Document* pSrcPDFDoc);
+  ~CPDF_PageExporter();
+
+  // For the pages from the source document with |pageNums| as their page
+  // numbers, insert them into the destination document at page |nIndex|.
+  // |pageNums| is 1-based.
+  // |nIndex| is 0-based.
+  bool ExportPage(const std::vector<uint32_t>& pageNums, int nIndex);
+};
+
+CPDF_PageExporter::CPDF_PageExporter(CPDF_Document* pDestPDFDoc,
+                                     CPDF_Document* pSrcPDFDoc)
+    : CPDF_PageOrganizer(pDestPDFDoc, pSrcPDFDoc) {}
+
+CPDF_PageExporter::~CPDF_PageExporter() = default;
+
+bool CPDF_PageExporter::ExportPage(const std::vector<uint32_t>& pageNums,
+                                   int nIndex) {
+  if (!PDFDocInit())
+    return false;
+
+  int curpage = nIndex;
+  auto pObjNumberMap = pdfium::MakeUnique<ObjectNumberMap>();
+  for (size_t i = 0; i < pageNums.size(); ++i) {
+    CPDF_Dictionary* pCurPageDict = dest()->CreateNewPage(curpage);
+    CPDF_Dictionary* pSrcPageDict = src()->GetPage(pageNums[i] - 1);
+    if (!pSrcPageDict || !pCurPageDict)
+      return false;
+
+    // Clone the page dictionary
+    for (const auto& it : *pSrcPageDict) {
+      const ByteString& cbSrcKeyStr = it.first;
+      if (cbSrcKeyStr == "Type" || cbSrcKeyStr == "Parent")
+        continue;
+
+      CPDF_Object* pObj = it.second.get();
+      pCurPageDict->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(pCurPageDict, pSrcPageDict, "MediaBox")) {
+      // Search for "CropBox" in the source page dictionary.
+      // If it does not exist, use the default letter size.
+      CPDF_Object* pInheritable =
+          PageDictGetInheritableTag(pSrcPageDict, "CropBox");
+      if (pInheritable) {
+        pCurPageDict->SetFor("MediaBox", pInheritable->Clone());
+      } else {
+        // Make the default size letter size (8.5"x11")
+        CPDF_Array* pArray = pCurPageDict->SetNewFor<CPDF_Array>("MediaBox");
+        pArray->AddNew<CPDF_Number>(0);
+        pArray->AddNew<CPDF_Number>(0);
+        pArray->AddNew<CPDF_Number>(612);
+        pArray->AddNew<CPDF_Number>(792);
+      }
+    }
+
+    // 2 Resources - required
+    if (!CopyInheritable(pCurPageDict, pSrcPageDict, "Resources")) {
+      // Use a default empty resources if it does not exist.
+      pCurPageDict->SetNewFor<CPDF_Dictionary>("Resources");
+    }
+
+    // 3 CropBox - optional
+    CopyInheritable(pCurPageDict, pSrcPageDict, "CropBox");
+    // 4 Rotate - optional
+    CopyInheritable(pCurPageDict, pSrcPageDict, "Rotate");
+
+    // Update the reference
+    uint32_t dwOldPageObj = pSrcPageDict->GetObjNum();
+    uint32_t dwNewPageObj = pCurPageDict->GetObjNum();
+    (*pObjNumberMap)[dwOldPageObj] = dwNewPageObj;
+    UpdateReference(pCurPageDict, pObjNumberMap.get());
+    ++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 : public CPDF_PageOrganizer {
+ public:
+  CPDF_NPageToOneExporter(CPDF_Document* pDestPDFDoc,
+                          CPDF_Document* pSrcPDFDoc);
+  ~CPDF_NPageToOneExporter();
+
+  // For the pages from the source document with |pageNums| as their page
+  // numbers, insert them into the destination document, starting at page 0.
+  // |pageNums| is 1-based.
+  // |destPageWidth| and |destPageHeight| are the destination document page
+  // dimensions, measured in pixels.
+  // |numPagesOnXAxis| and |numPagesOnXAxis| together defines how many source
+  // pages fit on one destination page.
+  bool ExportNPagesToOne(const std::vector<uint32_t>& pageNums,
+                         float destPageWidth,
+                         float destPageHeight,
+                         unsigned int numPagesOnXAxis,
+                         unsigned int numPagesOnYAxis);
+
+ private:
+  // Map page object number to XObject object name.
+  using PageXObjectMap = std::map<uint32_t, ByteString>;
+  // Map XObject's object name to it's object number.
+  using XObjectNameNumberMap = std::map<ByteString, uint32_t>;
+
+  static void SetMediaBox(CPDF_Dictionary* pDestPageDict,
+                          const CFX_SizeF& pagesize);
+
+  // Creates a xobject from the source page dictionary, and appends the
+  // bsContent string with the xobject reference surrounded by the
+  // transformation matrix.
+  void AddSubPage(CPDF_Dictionary* pPageDict,
+                  const CFX_PointF& position,
+                  float scale,
+                  ObjectNumberMap* pObjNumberMap,
+                  PageXObjectMap* pPageXObjectMap,
+                  XObjectNameNumberMap* pXObjNameNumberMap,
+                  ByteString* bsContent);
+  uint32_t MakeXObject(CPDF_Dictionary* pSrcPageDict,
+                       ObjectNumberMap* pObjNumberMap,
+                       CPDF_Document* pDestDoc);
+
+  void FinishPage(CPDF_Dictionary* pCurPageDict,
+                  const ByteString& bsContent,
+                  XObjectNameNumberMap* pXObjNameNumberMap);
+
+  uint32_t m_xobjectNum = 0;
+  CFX_SizeF m_pageSize;
+  XObjectNameNumberMap m_xobjs;
+};
+
+CPDF_NPageToOneExporter::CPDF_NPageToOneExporter(CPDF_Document* pDestPDFDoc,
+                                                 CPDF_Document* pSrcPDFDoc)
+    : CPDF_PageOrganizer(pDestPDFDoc, pSrcPDFDoc) {}
+
+CPDF_NPageToOneExporter::~CPDF_NPageToOneExporter() = default;
+
+bool CPDF_NPageToOneExporter::ExportNPagesToOne(
+    const std::vector<uint32_t>& pageNums,
+    float destPageWidth,
+    float destPageHeight,
+    unsigned int numPagesOnXAxis,
+    unsigned int numPagesOnYAxis) {
+  if (!PDFDocInit())
+    return false;
+
+  FX_SAFE_SIZE_T safe_numPagesPerSheet = numPagesOnXAxis;
+  safe_numPagesPerSheet *= numPagesOnYAxis;
+  if (!safe_numPagesPerSheet.IsValid())
+    return false;
+
+  size_t numPagesPerSheet = safe_numPagesPerSheet.ValueOrDie();
+
+  // Mapping of source page object number and XObject object number.
+  // Used to update refernece.
+  ObjectNumberMap objectNumberMap;
+  // Mapping of source page object number and XObject name of the entire doc.
+  // If there are two pages that are identical and have the same object number,
+  // we can reuse one created XObject.
+  PageXObjectMap pageXObjectMap;
+  const CFX_SizeF pagesize(destPageWidth, destPageHeight);
+  NupState nupState(destPageWidth, destPageHeight, numPagesOnXAxis,
+                    numPagesOnYAxis);
+
+  size_t curpage = 0;
+  for (size_t outerPage = 0; outerPage < pageNums.size();
+       outerPage += numPagesPerSheet) {
+    // Create a new page
+    CPDF_Dictionary* pCurPageDict = dest()->CreateNewPage(curpage);
+    if (!pCurPageDict)
+      return false;
+
+    SetMediaBox(pCurPageDict, pagesize);
+    ByteString bsContent;
+    size_t innerPageMax =
+        std::min(outerPage + numPagesPerSheet, pageNums.size());
+    // Mapping of XObject name and XObject object number of one page.
+    XObjectNameNumberMap xObjectNameNumberMap;
+    for (size_t innerPage = outerPage; innerPage < innerPageMax; ++innerPage) {
+      CPDF_Dictionary* pSrcPageDict = src()->GetPage(pageNums[innerPage] - 1);
+      if (!pSrcPageDict)
+        return false;
+
+      CPDF_Page srcPage(src(), pSrcPageDict, true);
+      NupPageSettings pgEdit;
+      nupState.CalculateNewPagePosition(srcPage.GetPageWidth(),
+                                        srcPage.GetPageHeight(), &pgEdit);
+      AddSubPage(pSrcPageDict, pgEdit.subPageStartPoint, pgEdit.scale,
+                 &objectNumberMap, &pageXObjectMap, &xObjectNameNumberMap,
+                 &bsContent);
+    }
+
+    // Finish up the current page.
+    FinishPage(pCurPageDict, bsContent, &xObjectNameNumberMap);
+    ++curpage;
+  }
+
+  return true;
+}
+
+// static
+void CPDF_NPageToOneExporter::SetMediaBox(CPDF_Dictionary* pDestPageDict,
+                                          const CFX_SizeF& pagesize) {
+  CPDF_Array* pArray = pDestPageDict->SetNewFor<CPDF_Array>("MediaBox");
+  pArray->AddNew<CPDF_Number>(0);
+  pArray->AddNew<CPDF_Number>(0);
+  pArray->AddNew<CPDF_Number>(pagesize.width);
+  pArray->AddNew<CPDF_Number>(pagesize.height);
+}
+
+void CPDF_NPageToOneExporter::AddSubPage(
+    CPDF_Dictionary* pPageDict,
+    const CFX_PointF& position,
+    float scale,
+    ObjectNumberMap* pObjNumberMap,
+    PageXObjectMap* pPageXObjectMap,
+    XObjectNameNumberMap* pXObjNameNumberMap,
+    ByteString* bsContent) {
+  uint32_t dwPageObjnum = pPageDict->GetObjNum();
+  ByteString bsXObjectName;
+  const auto it = pPageXObjectMap->find(dwPageObjnum);
+  if (it != pPageXObjectMap->end()) {
+    bsXObjectName = it->second;
+  } else {
+    ++m_xobjectNum;
+    // TODO(Xlou): A better name schema to avoid possible object name collision.
+    bsXObjectName = ByteString::Format("X%d", m_xobjectNum);
+    m_xobjs[bsXObjectName] = MakeXObject(pPageDict, pObjNumberMap, dest());
+    (*pPageXObjectMap)[dwPageObjnum] = bsXObjectName;
+  }
+  (*pXObjNameNumberMap)[bsXObjectName] = m_xobjs[bsXObjectName];
+
+  CFX_Matrix matrix;
+  matrix.Scale(scale, scale);
+  matrix.Translate(position.x, position.y);
+
+  std::ostringstream contentStream;
+  contentStream << "q\n"
+                << matrix.a << " " << matrix.b << " " << matrix.c << " "
+                << matrix.d << " " << matrix.e << " " << matrix.f << " cm\n"
+                << "/" << bsXObjectName << " Do Q\n";
+  *bsContent += ByteString(contentStream);
+}
+
+uint32_t CPDF_NPageToOneExporter::MakeXObject(CPDF_Dictionary* pSrcPageDict,
+                                              ObjectNumberMap* pObjNumberMap,
+                                              CPDF_Document* pDestDoc) {
+  ASSERT(pSrcPageDict);
+
+  CPDF_Object* pSrcContentObj = GetPageOrganizerPageContent(pSrcPageDict);
+  CPDF_Stream* pNewXObject = pDestDoc->NewIndirect<CPDF_Stream>(
+      nullptr, 0,
+      pdfium::MakeUnique<CPDF_Dictionary>(pDestDoc->GetByteStringPool()));
+  CPDF_Dictionary* pNewXObjectDict = pNewXObject->GetDict();
+  const ByteString bsResourceString = "Resources";
+  if (!CopyInheritable(pNewXObjectDict, pSrcPageDict, bsResourceString)) {
+    // Use a default empty resources if it does not exist.
+    pNewXObjectDict->SetNewFor<CPDF_Dictionary>(bsResourceString);
+  }
+  uint32_t dwSrcPageObj = pSrcPageDict->GetObjNum();
+  uint32_t dwNewXobjectObj = pNewXObjectDict->GetObjNum();
+  (*pObjNumberMap)[dwSrcPageObj] = dwNewXobjectObj;
+  UpdateReference(pNewXObjectDict, pObjNumberMap);
+
+  pNewXObjectDict->SetNewFor<CPDF_Name>("Type", "XObject");
+  pNewXObjectDict->SetNewFor<CPDF_Name>("Subtype", "Form");
+  pNewXObjectDict->SetNewFor<CPDF_Number>("FormType", 1);
+  pNewXObjectDict->SetRectFor("BBox", GetTrimBox(pSrcPageDict));
+  // TODO(xlou): add matrix field to pNewXObjectDict.
+
+  if (CPDF_Array* pSrcContentArray = ToArray(pSrcContentObj)) {
+    ByteString bsSrcContentStream;
+    for (size_t i = 0; i < pSrcContentArray->GetCount(); ++i) {
+      CPDF_Stream* pStream = pSrcContentArray->GetStreamAt(i);
+      auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
+      pAcc->LoadAllDataFiltered();
+      ByteString bsStream(pAcc->GetData(), pAcc->GetSize());
+      bsSrcContentStream += bsStream;
+      bsSrcContentStream += "\n";
+    }
+    pNewXObject->SetDataAndRemoveFilter(bsSrcContentStream.raw_str(),
+                                        bsSrcContentStream.GetLength());
+  } else {
+    CPDF_Stream* pStream = pSrcContentObj->AsStream();
+    auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
+    pAcc->LoadAllDataFiltered();
+    ByteString bsStream(pAcc->GetData(), pAcc->GetSize());
+    pNewXObject->SetDataAndRemoveFilter(bsStream.raw_str(),
+                                        bsStream.GetLength());
+  }
+
+  return pNewXObject->GetObjNum();
+}
+
+void CPDF_NPageToOneExporter::FinishPage(
+    CPDF_Dictionary* pCurPageDict,
+    const ByteString& bsContent,
+    XObjectNameNumberMap* pXObjNameNumberMap) {
+  ASSERT(pCurPageDict);
+
+  CPDF_Dictionary* pRes = pCurPageDict->GetDictFor("Resources");
+  if (!pRes)
+    pRes = pCurPageDict->SetNewFor<CPDF_Dictionary>("Resources");
+
+  CPDF_Dictionary* pPageXObject = pRes->GetDictFor("XObject");
+  if (!pPageXObject)
+    pPageXObject = pRes->SetNewFor<CPDF_Dictionary>("XObject");
+
+  for (auto& it : *pXObjNameNumberMap) {
+    pPageXObject->SetNewFor<CPDF_Reference>(it.first, dest(), it.second);
+  }
+
+  auto pDict = pdfium::MakeUnique<CPDF_Dictionary>(dest()->GetByteStringPool());
+  CPDF_Stream* pStream =
+      dest()->NewIndirect<CPDF_Stream>(nullptr, 0, std::move(pDict));
+  pStream->SetData(bsContent.raw_str(), bsContent.GetLength());
+  pCurPageDict->SetNewFor<CPDF_Reference>("Contents", dest(),
+                                          pStream->GetObjNum());
+}
+
+}  // namespace
+
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc,
                                                      FPDF_DOCUMENT src_doc,
                                                      FPDF_BYTESTRING pagerange,
@@ -738,12 +792,8 @@
   if (page_numbers.empty())
     return false;
 
-  CPDF_PageOrganizer pageOrg(pDestDoc, pSrcDoc);
-
-  if (!pageOrg.PDFDocInit())
-    return false;
-
-  return pageOrg.ExportPage(page_numbers, index);
+  CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
+  return exporter.ExportPage(page_numbers, index);
 }
 
 FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV
@@ -773,13 +823,18 @@
   if (page_numbers.empty())
     return nullptr;
 
-  CPDF_PageOrganizer pageOrg(pDestDoc, pSrcDoc);
-  if (!pageOrg.PDFDocInit() ||
-      !pageOrg.ExportNPagesToOne(page_numbers, output_width, output_height,
-                                 num_pages_on_x_axis, num_pages_on_y_axis)) {
-    return nullptr;
+  if (num_pages_on_x_axis == 1 && num_pages_on_y_axis == 1) {
+    CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
+    if (!exporter.ExportPage(page_numbers, 0))
+      return nullptr;
+    return output_doc.release();
   }
 
+  CPDF_NPageToOneExporter exporter(pDestDoc, pSrcDoc);
+  if (!exporter.ExportNPagesToOne(page_numbers, output_width, output_height,
+                                  num_pages_on_x_axis, num_pages_on_y_axis)) {
+    return nullptr;
+  }
   return output_doc.release();
 }