Return page indices from ParsePageRangeString()

Almost everywhere else in PDFium, pages are enumerated using 0-based
indices instead of 1-based numbers. Consequently, callers of
ParsePageRangeString() have to re-convert the return value of the
function.

Meanwhile:
- Update the callers to use indices directly.
- Use spans in more places.
- Update unit tests to use matchers.
Change-Id: I140ac484a775662472a8c0edcb85a8d645868624
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/81977
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
diff --git a/fpdfsdk/cpdfsdk_helpers.cpp b/fpdfsdk/cpdfsdk_helpers.cpp
index 30db260..11b9872 100644
--- a/fpdfsdk/cpdfsdk_helpers.cpp
+++ b/fpdfsdk/cpdfsdk_helpers.cpp
@@ -471,7 +471,7 @@
           pdfium::base::checked_cast<uint32_t>(atoi(args[0].c_str()));
       if (page_num == 0 || page_num > nCount)
         return std::vector<uint32_t>();
-      results.push_back(page_num);
+      results.push_back(page_num - 1);
     } else if (args.size() == 2) {
       uint32_t first_num =
           pdfium::base::checked_cast<uint32_t>(atoi(args[0].c_str()));
@@ -482,7 +482,7 @@
       if (last_num == 0 || first_num > last_num || last_num > nCount)
         return std::vector<uint32_t>();
       for (uint32_t i = first_num; i <= last_num; ++i)
-        results.push_back(i);
+        results.push_back(i - 1);
     } else {
       return std::vector<uint32_t>();
     }
diff --git a/fpdfsdk/cpdfsdk_helpers.h b/fpdfsdk/cpdfsdk_helpers.h
index 9330d9a..126fa7f 100644
--- a/fpdfsdk/cpdfsdk_helpers.h
+++ b/fpdfsdk/cpdfsdk_helpers.h
@@ -281,6 +281,7 @@
 void SetColorFromScheme(const FPDF_COLORSCHEME* pColorScheme,
                         CPDF_RenderOptions* pRenderOptions);
 
+// Returns a vector of page indices given a page range string.
 std::vector<uint32_t> ParsePageRangeString(const ByteString& bsPageRange,
                                            uint32_t nCount);
 
diff --git a/fpdfsdk/cpdfsdk_helpers_unittest.cpp b/fpdfsdk/cpdfsdk_helpers_unittest.cpp
index 21b0061..0536c6a 100644
--- a/fpdfsdk/cpdfsdk_helpers_unittest.cpp
+++ b/fpdfsdk/cpdfsdk_helpers_unittest.cpp
@@ -4,8 +4,12 @@
 
 #include "fpdfsdk/cpdfsdk_helpers.h"
 
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+
 TEST(CPDFSDK_HelpersTest, NulTerminateMaybeCopyAndReturnLength) {
   {
     const ByteString to_be_copied("toBeCopied");
@@ -40,50 +44,45 @@
 }
 
 TEST(CPDFSDK_HelpersTest, ParsePageRangeString) {
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("", 1));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString(" ", 1));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("clams", 1));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("0", 0));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1", 0));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString(",1", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1,", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1,clams", 1));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("clams,1", 1));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("0-1", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1-0", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1-5", 4));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1-11,", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString(",1-1", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1-", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1-,", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("-2,", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1-clams", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("clams-1,", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1-2clams", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("0,1", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1,0", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1-2,,,,3-4", 10));
-  EXPECT_EQ(std::vector<uint32_t>({}), ParsePageRangeString("1-2-", 10));
-  EXPECT_EQ(std::vector<uint32_t>({1}), ParsePageRangeString("1-1", 10));
-  EXPECT_EQ(std::vector<uint32_t>{1}, ParsePageRangeString("1", 1));
-  EXPECT_EQ(std::vector<uint32_t>({1, 2, 3, 4}),
-            ParsePageRangeString("1-4", 4));
-  EXPECT_EQ(std::vector<uint32_t>({1, 2, 3, 4}),
-            ParsePageRangeString("1- 4", 4));
-  EXPECT_EQ(std::vector<uint32_t>({1, 2, 3, 4}),
-            ParsePageRangeString("1 -4", 4));
-  EXPECT_EQ(std::vector<uint32_t>({1, 2}), ParsePageRangeString("1,2", 10));
-  EXPECT_EQ(std::vector<uint32_t>({2, 1}), ParsePageRangeString("2,1", 10));
-  EXPECT_EQ(std::vector<uint32_t>({1, 50, 2}),
-            ParsePageRangeString("1,50,2", 100));
-  EXPECT_EQ(std::vector<uint32_t>({1, 2, 3, 4, 50}),
-            ParsePageRangeString("1-4,50", 100));
-  EXPECT_EQ(std::vector<uint32_t>({50, 1, 2}),
-            ParsePageRangeString("50,1-2", 100));
-  EXPECT_EQ(std::vector<uint32_t>({50, 1, 2}),
-            ParsePageRangeString("5  0, 1-2 ", 100));  // ???
-  EXPECT_EQ(std::vector<uint32_t>({1, 2, 3, 4, 5, 6}),
-            ParsePageRangeString("1-3,4-6", 10));
-  EXPECT_EQ(std::vector<uint32_t>({1, 2, 3, 4, 3, 4, 5, 6}),
-            ParsePageRangeString("1-4,3-6", 10));
+  EXPECT_THAT(ParsePageRangeString("", 1), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString(" ", 1), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("clams", 1), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("0", 0), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1", 0), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString(",1", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1,", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1,clams", 1), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("clams,1", 1), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("0-1", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1-0", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1-5", 4), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1-11,", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString(",1-1", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1-", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1-,", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("-2,", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1-clams", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("clams-1,", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1-2clams", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("0,1", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1,0", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1-2,,,,3-4", 10), IsEmpty());
+  EXPECT_THAT(ParsePageRangeString("1-2-", 10), IsEmpty());
+
+  EXPECT_THAT(ParsePageRangeString("1-1", 10), ElementsAre(0));
+  EXPECT_THAT(ParsePageRangeString("1", 1), ElementsAre(0));
+  EXPECT_THAT(ParsePageRangeString("1-4", 4), ElementsAre(0, 1, 2, 3));
+  EXPECT_THAT(ParsePageRangeString("1- 4", 4), ElementsAre(0, 1, 2, 3));
+  EXPECT_THAT(ParsePageRangeString("1 -4", 4), ElementsAre(0, 1, 2, 3));
+  EXPECT_THAT(ParsePageRangeString("1,2", 10), ElementsAre(0, 1));
+  EXPECT_THAT(ParsePageRangeString("2,1", 10), ElementsAre(1, 0));
+  EXPECT_THAT(ParsePageRangeString("1,50,2", 100), ElementsAre(0, 49, 1));
+  EXPECT_THAT(ParsePageRangeString("1-4,50", 100), ElementsAre(0, 1, 2, 3, 49));
+  EXPECT_THAT(ParsePageRangeString("50,1-2", 100), ElementsAre(49, 0, 1));
+  EXPECT_THAT(ParsePageRangeString("5  0, 1-2 ", 100),
+              ElementsAre(49, 0, 1));  // ???
+  EXPECT_THAT(ParsePageRangeString("1-3,4-6", 10),
+              ElementsAre(0, 1, 2, 3, 4, 5));
+  EXPECT_THAT(ParsePageRangeString("1-4,3-6", 10),
+              ElementsAre(0, 1, 2, 3, 2, 3, 4, 5));
 }
diff --git a/fpdfsdk/fpdf_ppo.cpp b/fpdfsdk/fpdf_ppo.cpp
index abd3340..3e46ed8 100644
--- a/fpdfsdk/fpdf_ppo.cpp
+++ b/fpdfsdk/fpdf_ppo.cpp
@@ -9,6 +9,7 @@
 #include <algorithm>
 #include <map>
 #include <memory>
+#include <numeric>
 #include <sstream>
 #include <utility>
 #include <vector>
@@ -33,6 +34,7 @@
 #include "fpdfsdk/cpdfsdk_helpers.h"
 #include "public/cpp/fpdf_scopers.h"
 #include "third_party/base/check.h"
+#include "third_party/base/span.h"
 
 namespace {
 
@@ -201,17 +203,15 @@
   return true;
 }
 
-std::vector<uint32_t> GetPageNumbers(const CPDF_Document& doc,
+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_numbers;
-  for (uint32_t i = 1; i <= nCount; ++i)
-    page_numbers.push_back(i);
-
-  return page_numbers;
+  std::vector<uint32_t> page_indices(nCount);
+  std::iota(page_indices.begin(), page_indices.end(), 0);
+  return page_indices;
 }
 
 class CPDF_PageOrganizer {
@@ -382,11 +382,10 @@
   CPDF_PageExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
   ~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);
+  // 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,
@@ -395,15 +394,15 @@
 
 CPDF_PageExporter::~CPDF_PageExporter() = default;
 
-bool CPDF_PageExporter::ExportPage(const std::vector<uint32_t>& pageNums,
+bool CPDF_PageExporter::ExportPage(pdfium::span<const uint32_t> pageIndices,
                                    int nIndex) {
   if (!Init())
     return false;
 
   int curpage = nIndex;
-  for (size_t i = 0; i < pageNums.size(); ++i) {
+  for (uint32_t pageIndex : pageIndices) {
     CPDF_Dictionary* pDestPageDict = dest()->CreateNewPage(curpage);
-    auto* pSrcPageDict = src()->GetPageDictionary(pageNums[i] - 1);
+    auto* pSrcPageDict = src()->GetPageDictionary(pageIndex);
     if (!pSrcPageDict || !pDestPageDict)
       return false;
 
@@ -473,14 +472,15 @@
   CPDF_NPageToOneExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
   ~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.
+  // 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(const std::vector<uint32_t>& pageNums,
+  bool ExportNPagesToOne(pdfium::span<const uint32_t> pageIndices,
                          const CFX_SizeF& destPageSize,
                          size_t nPagesOnXAxis,
                          size_t nPagesOnYAxis);
@@ -525,7 +525,7 @@
 CPDF_NPageToOneExporter::~CPDF_NPageToOneExporter() = default;
 
 bool CPDF_NPageToOneExporter::ExportNPagesToOne(
-    const std::vector<uint32_t>& pageNums,
+    pdfium::span<const uint32_t> pageIndices,
     const CFX_SizeF& destPageSize,
     size_t nPagesOnXAxis,
     size_t nPagesOnYAxis) {
@@ -545,7 +545,7 @@
   size_t curpage = 0;
   const CFX_FloatRect destPageRect(0, 0, destPageSize.width,
                                    destPageSize.height);
-  for (size_t iOuterPage = 0; iOuterPage < pageNums.size();
+  for (size_t iOuterPage = 0; iOuterPage < pageIndices.size();
        iOuterPage += nPagesPerSheet) {
     m_XObjectNameToNumberMap.clear();
 
@@ -557,9 +557,9 @@
     pDestPageDict->SetRectFor(pdfium::page_object::kMediaBox, destPageRect);
     ByteString bsContent;
     size_t iInnerPageMax =
-        std::min(iOuterPage + nPagesPerSheet, pageNums.size());
+        std::min(iOuterPage + nPagesPerSheet, pageIndices.size());
     for (size_t i = iOuterPage; i < iInnerPageMax; ++i) {
-      auto* pSrcPageDict = src()->GetPageDictionary(pageNums[i] - 1);
+      auto* pSrcPageDict = src()->GetPageDictionary(pageIndices[i]);
       if (!pSrcPageDict)
         return false;
 
@@ -692,12 +692,12 @@
   if (!pSrcDoc)
     return false;
 
-  std::vector<uint32_t> page_numbers = GetPageNumbers(*pSrcDoc, pagerange);
-  if (page_numbers.empty())
+  std::vector<uint32_t> page_indices = GetPageIndices(*pSrcDoc, pagerange);
+  if (page_indices.empty())
     return false;
 
   CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
-  return exporter.ExportPage(page_numbers, index);
+  return exporter.ExportPage(page_indices, index);
 }
 
 FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV
@@ -722,19 +722,19 @@
   CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(output_doc.get());
   DCHECK(pDestDoc);
 
-  std::vector<uint32_t> page_numbers = GetPageNumbers(*pSrcDoc, ByteString());
-  if (page_numbers.empty())
+  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_numbers, 0))
+    if (!exporter.ExportPage(page_indices, 0))
       return nullptr;
     return output_doc.release();
   }
 
   CPDF_NPageToOneExporter exporter(pDestDoc, pSrcDoc);
-  if (!exporter.ExportNPagesToOne(page_numbers,
+  if (!exporter.ExportNPagesToOne(page_indices,
                                   CFX_SizeF(output_width, output_height),
                                   num_pages_on_x_axis, num_pages_on_y_axis)) {
     return nullptr;