Add experimental FPDF_ImportPagesByIndex() API

Introduce an FPDF_ImportPages() alternative that takes an array of
indices for the pages to import, instead of a string of page ranges.

Bug: chromium:1200000
Change-Id: I2e7b39e464ca46e113b7223c4c11458de243762e
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/81810
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_ppo.cpp b/fpdfsdk/fpdf_ppo.cpp
index 3e46ed8..614e5ab 100644
--- a/fpdfsdk/fpdf_ppo.cpp
+++ b/fpdfsdk/fpdf_ppo.cpp
@@ -680,6 +680,37 @@
 
 }  // 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;
+
+  return exporter.ExportPage(
+      pdfium::make_span(reinterpret_cast<const uint32_t*>(page_indices),
+                        length),
+      index);
+}
+
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc,
                                                      FPDF_DOCUMENT src_doc,
                                                      FPDF_BYTESTRING pagerange,
diff --git a/fpdfsdk/fpdf_ppo_embeddertest.cpp b/fpdfsdk/fpdf_ppo_embeddertest.cpp
index e063737..84d52e9 100644
--- a/fpdfsdk/fpdf_ppo_embeddertest.cpp
+++ b/fpdfsdk/fpdf_ppo_embeddertest.cpp
@@ -12,6 +12,7 @@
 #include "public/fpdfview.h"
 #include "testing/embedder_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/base/stl_util.h"
 
 namespace {
 
@@ -43,6 +44,25 @@
   FPDF_CloseDocument(output_doc);
 }
 
+TEST_F(FPDFPPOEmbedderTest, ImportPagesByIndex) {
+  ASSERT_TRUE(OpenDocument("viewer_ref.pdf"));
+
+  FPDF_PAGE page = LoadPage(0);
+  EXPECT_TRUE(page);
+
+  ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
+  ASSERT_TRUE(output_doc);
+  EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc.get(), document()));
+
+  static constexpr int kPageIndices[] = {1};
+  EXPECT_TRUE(FPDF_ImportPagesByIndex(output_doc.get(), document(),
+                                      kPageIndices, pdfium::size(kPageIndices),
+                                      0));
+  EXPECT_EQ(1, FPDF_GetPageCount(output_doc.get()));
+
+  UnloadPage(page);
+}
+
 TEST_F(FPDFPPOEmbedderTest, ImportPages) {
   ASSERT_TRUE(OpenDocument("viewer_ref.pdf"));
 
@@ -160,6 +180,79 @@
   FPDF_CloseDocument(output_doc);
 }
 
+TEST_F(FPDFPPOEmbedderTest, BadIndices) {
+  ASSERT_TRUE(OpenDocument("hello_world.pdf"));
+
+  FPDF_PAGE page = LoadPage(0);
+  EXPECT_TRUE(page);
+
+  ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
+  EXPECT_TRUE(output_doc);
+
+  static constexpr int kBadIndices1[] = {-1};
+  EXPECT_FALSE(FPDF_ImportPagesByIndex(output_doc.get(), document(),
+                                       kBadIndices1, pdfium::size(kBadIndices1),
+                                       0));
+
+  static constexpr int kBadIndices2[] = {1};
+  EXPECT_FALSE(FPDF_ImportPagesByIndex(output_doc.get(), document(),
+                                       kBadIndices2, pdfium::size(kBadIndices2),
+                                       0));
+
+  static constexpr int kBadIndices3[] = {-1, 0, 1};
+  EXPECT_FALSE(FPDF_ImportPagesByIndex(output_doc.get(), document(),
+                                       kBadIndices3, pdfium::size(kBadIndices3),
+                                       0));
+
+  static constexpr int kBadIndices4[] = {42};
+  EXPECT_FALSE(FPDF_ImportPagesByIndex(output_doc.get(), document(),
+                                       kBadIndices4, pdfium::size(kBadIndices4),
+                                       0));
+
+  UnloadPage(page);
+}
+
+TEST_F(FPDFPPOEmbedderTest, GoodIndices) {
+  ASSERT_TRUE(OpenDocument("viewer_ref.pdf"));
+
+  FPDF_PAGE page = LoadPage(0);
+  EXPECT_TRUE(page);
+
+  ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
+  EXPECT_TRUE(output_doc);
+
+  static constexpr int kGoodIndices1[] = {0, 0, 0, 0};
+  EXPECT_TRUE(FPDF_ImportPagesByIndex(output_doc.get(), document(),
+                                      kGoodIndices1,
+                                      pdfium::size(kGoodIndices1), 0));
+  EXPECT_EQ(4, FPDF_GetPageCount(output_doc.get()));
+
+  static constexpr int kGoodIndices2[] = {0};
+  EXPECT_TRUE(FPDF_ImportPagesByIndex(output_doc.get(), document(),
+                                      kGoodIndices2,
+                                      pdfium::size(kGoodIndices2), 0));
+  EXPECT_EQ(5, FPDF_GetPageCount(output_doc.get()));
+
+  static constexpr int kGoodIndices3[] = {4};
+  EXPECT_TRUE(FPDF_ImportPagesByIndex(output_doc.get(), document(),
+                                      kGoodIndices3,
+                                      pdfium::size(kGoodIndices3), 0));
+  EXPECT_EQ(6, FPDF_GetPageCount(output_doc.get()));
+
+  static constexpr int kGoodIndices4[] = {1, 2, 3};
+  EXPECT_TRUE(FPDF_ImportPagesByIndex(output_doc.get(), document(),
+                                      kGoodIndices4,
+                                      pdfium::size(kGoodIndices4), 0));
+  EXPECT_EQ(9, FPDF_GetPageCount(output_doc.get()));
+
+  // Passing in a nullptr should import all the pages.
+  EXPECT_TRUE(
+      FPDF_ImportPagesByIndex(output_doc.get(), document(), nullptr, 0, 0));
+  EXPECT_EQ(14, FPDF_GetPageCount(output_doc.get()));
+
+  UnloadPage(page);
+}
+
 TEST_F(FPDFPPOEmbedderTest, BadRanges) {
   ASSERT_TRUE(OpenDocument("hello_world.pdf"));
 
@@ -213,7 +306,10 @@
 
   FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument();
   EXPECT_TRUE(output_doc);
-  EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1", 0));
+
+  static constexpr int kIndices[] = {0};
+  EXPECT_TRUE(FPDF_ImportPagesByIndex(output_doc, document(), kIndices,
+                                      pdfium::size(kIndices), 0));
   FPDF_CloseDocument(output_doc);
 
   UnloadPage(page);
@@ -248,7 +344,10 @@
 
   FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument();
   ASSERT_TRUE(output_doc);
-  EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1,2,3,4", 0));
+
+  static constexpr int kIndices[] = {0, 1, 2, 3};
+  EXPECT_TRUE(FPDF_ImportPagesByIndex(output_doc, document(), kIndices,
+                                      pdfium::size(kIndices), 0));
   ASSERT_EQ(4, FPDF_GetPageCount(output_doc));
   for (size_t i = 0; i < 4; ++i) {
     FPDF_PAGE page = FPDF_LoadPage(output_doc, i);
@@ -280,7 +379,10 @@
 
   FPDF_DOCUMENT new_doc = FPDF_CreateNewDocument();
   EXPECT_TRUE(new_doc);
-  EXPECT_TRUE(FPDF_ImportPages(new_doc, document(), "1", 0));
+
+  static constexpr int kIndices[] = {0};
+  EXPECT_TRUE(FPDF_ImportPagesByIndex(new_doc, document(), kIndices,
+                                      pdfium::size(kIndices), 0));
 
   EXPECT_EQ(1, FPDF_GetPageCount(new_doc));
   FPDF_PAGE new_page = FPDF_LoadPage(new_doc, 0);
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 2a68e11..7297b61 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -309,6 +309,7 @@
     CHK(FPDF_CopyViewerPreferences);
     CHK(FPDF_ImportNPagesToOne);
     CHK(FPDF_ImportPages);
+    CHK(FPDF_ImportPagesByIndex);
 
     // fpdf_progressive.h
     CHK(FPDF_RenderPageBitmapWithColorScheme_Start);
diff --git a/public/fpdf_ppo.h b/public/fpdf_ppo.h
index e9f3f66..d27b788 100644
--- a/public/fpdf_ppo.h
+++ b/public/fpdf_ppo.h
@@ -14,15 +14,38 @@
 extern "C" {
 #endif
 
+// Experimental API.
+// Import pages to a FPDF_DOCUMENT.
+//
+//   dest_doc     - The destination document for the pages.
+//   src_doc      - The document to be imported.
+//   page_indices - An array of page indices to be imported. The first page is
+//                  zero. If |page_indices| is NULL, all pages from |src_doc|
+//                  are imported.
+//   length       - The length of the |page_indices| array.
+//   index        - The page index at which to insert the first imported page
+//                  into |dest_doc|. The first page is zero.
+//
+// Returns TRUE on success. Returns FALSE if any pages in |page_indices| is
+// invalid.
+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);
+
 // Import pages to a FPDF_DOCUMENT.
 //
 //   dest_doc  - The destination document for the pages.
 //   src_doc   - The document to be imported.
-//   pagerange - A page range string, Such as "1,3,5-7". If |pagerange| is NULL,
-//               all pages from |src_doc| are imported.
-//   index     - The page index to insert at.
+//   pagerange - A page range string, Such as "1,3,5-7". The first page is one.
+//               If |pagerange| is NULL, all pages from |src_doc| are imported.
+//   index     - The page index at which to insert the first imported page into
+//               |dest_doc|. The first page is zero.
 //
-// Returns TRUE on success.
+// Returns TRUE on success. Returns FALSE if any pages in |pagerange| is
+// invalid or if |pagerange| cannot be read.
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc,
                                                      FPDF_DOCUMENT src_doc,
                                                      FPDF_BYTESTRING pagerange,