Add ScopedFPDFTextFind.

For calling FPDFText_FindClose() automatically.

Change-Id: Iaeda33fc7e2aa3199ab657fac88ec8410398b8e7
Reviewed-on: https://pdfium-review.googlesource.com/c/50470
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_text_embeddertest.cpp b/fpdfsdk/fpdf_text_embeddertest.cpp
index c5a99d3..db2716a 100644
--- a/fpdfsdk/fpdf_text_embeddertest.cpp
+++ b/fpdfsdk/fpdf_text_embeddertest.cpp
@@ -209,94 +209,104 @@
   std::unique_ptr<unsigned short, pdfium::FreeDeleter> world_substr =
       GetFPDFWideString(L"orld");
 
-  // No occurences of "nope" in test page.
-  FPDF_SCHHANDLE search = FPDFText_FindStart(textpage, nope.get(), 0, 0);
-  EXPECT_TRUE(search);
-  EXPECT_EQ(0, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(0, FPDFText_GetSchCount(search));
+  {
+    // No occurences of "nope" in test page.
+    ScopedFPDFTextFind search(FPDFText_FindStart(textpage, nope.get(), 0, 0));
+    EXPECT_TRUE(search);
+    EXPECT_EQ(0, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(0, FPDFText_GetSchCount(search.get()));
 
-  // Advancing finds nothing.
-  EXPECT_FALSE(FPDFText_FindNext(search));
-  EXPECT_EQ(0, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(0, FPDFText_GetSchCount(search));
+    // Advancing finds nothing.
+    EXPECT_FALSE(FPDFText_FindNext(search.get()));
+    EXPECT_EQ(0, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(0, FPDFText_GetSchCount(search.get()));
 
-  // Retreating finds nothing.
-  EXPECT_FALSE(FPDFText_FindPrev(search));
-  EXPECT_EQ(0, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(0, FPDFText_GetSchCount(search));
-  FPDFText_FindClose(search);
+    // Retreating finds nothing.
+    EXPECT_FALSE(FPDFText_FindPrev(search.get()));
+    EXPECT_EQ(0, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(0, FPDFText_GetSchCount(search.get()));
+  }
 
-  // Two occurences of "world" in test page.
-  search = FPDFText_FindStart(textpage, world.get(), 0, 2);
-  EXPECT_TRUE(search);
+  {
+    // Two occurences of "world" in test page.
+    ScopedFPDFTextFind search(FPDFText_FindStart(textpage, world.get(), 0, 2));
+    EXPECT_TRUE(search);
 
-  // Remains not found until advanced.
-  EXPECT_EQ(0, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(0, FPDFText_GetSchCount(search));
+    // Remains not found until advanced.
+    EXPECT_EQ(0, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(0, FPDFText_GetSchCount(search.get()));
 
-  // First occurence of "world" in this test page.
-  EXPECT_TRUE(FPDFText_FindNext(search));
-  EXPECT_EQ(7, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(5, FPDFText_GetSchCount(search));
+    // First occurence of "world" in this test page.
+    EXPECT_TRUE(FPDFText_FindNext(search.get()));
+    EXPECT_EQ(7, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(5, FPDFText_GetSchCount(search.get()));
 
-  // Last occurence of "world" in this test page.
-  EXPECT_TRUE(FPDFText_FindNext(search));
-  EXPECT_EQ(24, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(5, FPDFText_GetSchCount(search));
+    // Last occurence of "world" in this test page.
+    EXPECT_TRUE(FPDFText_FindNext(search.get()));
+    EXPECT_EQ(24, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(5, FPDFText_GetSchCount(search.get()));
 
-  // Found position unchanged when fails to advance.
-  EXPECT_FALSE(FPDFText_FindNext(search));
-  EXPECT_EQ(24, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(5, FPDFText_GetSchCount(search));
+    // Found position unchanged when fails to advance.
+    EXPECT_FALSE(FPDFText_FindNext(search.get()));
+    EXPECT_EQ(24, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(5, FPDFText_GetSchCount(search.get()));
 
-  // Back to first occurence.
-  EXPECT_TRUE(FPDFText_FindPrev(search));
-  EXPECT_EQ(7, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(5, FPDFText_GetSchCount(search));
+    // Back to first occurence.
+    EXPECT_TRUE(FPDFText_FindPrev(search.get()));
+    EXPECT_EQ(7, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(5, FPDFText_GetSchCount(search.get()));
 
-  // Found position unchanged when fails to retreat.
-  EXPECT_FALSE(FPDFText_FindPrev(search));
-  EXPECT_EQ(7, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(5, FPDFText_GetSchCount(search));
-  FPDFText_FindClose(search);
+    // Found position unchanged when fails to retreat.
+    EXPECT_FALSE(FPDFText_FindPrev(search.get()));
+    EXPECT_EQ(7, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(5, FPDFText_GetSchCount(search.get()));
+  }
 
-  // Exact search unaffected by case sensitiity and whole word flags.
-  search = FPDFText_FindStart(textpage, world.get(),
-                              FPDF_MATCHCASE | FPDF_MATCHWHOLEWORD, 0);
-  EXPECT_TRUE(search);
-  EXPECT_TRUE(FPDFText_FindNext(search));
-  EXPECT_EQ(7, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(5, FPDFText_GetSchCount(search));
-  FPDFText_FindClose(search);
+  {
+    // Exact search unaffected by case sensitiity and whole word flags.
+    ScopedFPDFTextFind search(FPDFText_FindStart(
+        textpage, world.get(), FPDF_MATCHCASE | FPDF_MATCHWHOLEWORD, 0));
+    EXPECT_TRUE(search);
+    EXPECT_TRUE(FPDFText_FindNext(search.get()));
+    EXPECT_EQ(7, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(5, FPDFText_GetSchCount(search.get()));
+  }
 
-  // Default is case-insensitive, so matching agaist caps works.
-  search = FPDFText_FindStart(textpage, world_caps.get(), 0, 0);
-  EXPECT_TRUE(search);
-  EXPECT_TRUE(FPDFText_FindNext(search));
-  EXPECT_EQ(7, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(5, FPDFText_GetSchCount(search));
-  FPDFText_FindClose(search);
+  {
+    // Default is case-insensitive, so matching agaist caps works.
+    ScopedFPDFTextFind search(
+        FPDFText_FindStart(textpage, world_caps.get(), 0, 0));
+    EXPECT_TRUE(search);
+    EXPECT_TRUE(FPDFText_FindNext(search.get()));
+    EXPECT_EQ(7, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(5, FPDFText_GetSchCount(search.get()));
+  }
 
-  // But can be made case sensitive, in which case this fails.
-  search = FPDFText_FindStart(textpage, world_caps.get(), FPDF_MATCHCASE, 0);
-  EXPECT_FALSE(FPDFText_FindNext(search));
-  EXPECT_EQ(0, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(0, FPDFText_GetSchCount(search));
-  FPDFText_FindClose(search);
+  {
+    // But can be made case sensitive, in which case this fails.
+    ScopedFPDFTextFind search(
+        FPDFText_FindStart(textpage, world_caps.get(), FPDF_MATCHCASE, 0));
+    EXPECT_FALSE(FPDFText_FindNext(search.get()));
+    EXPECT_EQ(0, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(0, FPDFText_GetSchCount(search.get()));
+  }
 
-  // Default is match anywhere within word, so matching substirng works.
-  search = FPDFText_FindStart(textpage, world_substr.get(), 0, 0);
-  EXPECT_TRUE(FPDFText_FindNext(search));
-  EXPECT_EQ(8, FPDFText_GetSchResultIndex(search));
-  EXPECT_EQ(4, FPDFText_GetSchCount(search));
-  FPDFText_FindClose(search);
+  {
+    // Default is match anywhere within word, so matching substring works.
+    ScopedFPDFTextFind search(
+        FPDFText_FindStart(textpage, world_substr.get(), 0, 0));
+    EXPECT_TRUE(FPDFText_FindNext(search.get()));
+    EXPECT_EQ(8, FPDFText_GetSchResultIndex(search.get()));
+    EXPECT_EQ(4, FPDFText_GetSchCount(search.get()));
+  }
 
-  // But can be made to mach word boundaries, in which case this fails.
-  search =
-      FPDFText_FindStart(textpage, world_substr.get(), FPDF_MATCHWHOLEWORD, 0);
-  EXPECT_FALSE(FPDFText_FindNext(search));
-  // TODO(tsepez): investigate strange index/count values in this state.
-  FPDFText_FindClose(search);
+  {
+    // But can be made to mach word boundaries, in which case this fails.
+    ScopedFPDFTextFind search(FPDFText_FindStart(textpage, world_substr.get(),
+                                                 FPDF_MATCHWHOLEWORD, 0));
+    EXPECT_FALSE(FPDFText_FindNext(search.get()));
+    // TODO(tsepez): investigate strange index/count values in this state.
+  }
 
   FPDFText_ClosePage(textpage);
   UnloadPage(page);
diff --git a/public/cpp/fpdf_deleters.h b/public/cpp/fpdf_deleters.h
index 5275381..5cc0fb6 100644
--- a/public/cpp/fpdf_deleters.h
+++ b/public/cpp/fpdf_deleters.h
@@ -61,6 +61,10 @@
   inline void operator()(FPDF_STRUCTTREE tree) { FPDF_StructTree_Close(tree); }
 };
 
+struct FPDFTextFindDeleter {
+  inline void operator()(FPDF_SCHHANDLE handle) { FPDFText_FindClose(handle); }
+};
+
 struct FPDFTextPageDeleter {
   inline void operator()(FPDF_TEXTPAGE text) { FPDFText_ClosePage(text); }
 };
diff --git a/public/cpp/fpdf_scopers.h b/public/cpp/fpdf_scopers.h
index 3fe63e3..7d9cbc0 100644
--- a/public/cpp/fpdf_scopers.h
+++ b/public/cpp/fpdf_scopers.h
@@ -55,6 +55,10 @@
     std::unique_ptr<std::remove_pointer<FPDF_STRUCTTREE>::type,
                     FPDFStructTreeDeleter>;
 
+using ScopedFPDFTextFind =
+    std::unique_ptr<std::remove_pointer<FPDF_SCHHANDLE>::type,
+                    FPDFTextFindDeleter>;
+
 using ScopedFPDFTextPage =
     std::unique_ptr<std::remove_pointer<FPDF_TEXTPAGE>::type,
                     FPDFTextPageDeleter>;