Create FPDFPage_RemoveObject().

This call removes a page object from a page. We currently offer an
API to insert these objects, but not to remove.

Bug: pdfium:1037
Change-Id: I35ff596f9e7c87a39051f0cb1de40a5bec40fee5
Reviewed-on: https://pdfium-review.googlesource.com/28492
Reviewed-by: dsinclair <dsinclair@chromium.org>
Commit-Queue: Henrique Nakashima <hnakashima@chromium.org>
diff --git a/core/fpdfapi/page/cpdf_page.cpp b/core/fpdfapi/page/cpdf_page.cpp
index ba93f4a..0c8d63d 100644
--- a/core/fpdfapi/page/cpdf_page.cpp
+++ b/core/fpdfapi/page/cpdf_page.cpp
@@ -180,6 +180,20 @@
   return (rotate < 0) ? (rotate + 4) : rotate;
 }
 
+bool CPDF_Page::RemoveObject(CPDF_PageObject* pPageObj) {
+  pdfium::FakeUniquePtr<CPDF_PageObject> p(pPageObj);
+
+  auto* pPageObjectList = GetPageObjectList();
+  auto it =
+      std::find(std::begin(*pPageObjectList), std::end(*pPageObjectList), p);
+  if (it == std::end(*pPageObjectList))
+    return false;
+
+  it->release();
+  pPageObjectList->erase(it);
+  return true;
+}
+
 bool GraphicsData::operator<(const GraphicsData& other) const {
   if (fillAlpha != other.fillAlpha)
     return fillAlpha < other.fillAlpha;
diff --git a/core/fpdfapi/page/cpdf_page.h b/core/fpdfapi/page/cpdf_page.h
index c7aa12e..995d99c 100644
--- a/core/fpdfapi/page/cpdf_page.h
+++ b/core/fpdfapi/page/cpdf_page.h
@@ -54,6 +54,7 @@
 
   View* GetView() const { return m_pView; }
   void SetView(View* pView) { m_pView = pView; }
+  bool RemoveObject(CPDF_PageObject* pPageObj);
 
  private:
   void StartParse();
diff --git a/fpdfsdk/fpdfannot_embeddertest.cpp b/fpdfsdk/fpdfannot_embeddertest.cpp
index 70e184d..4e32b19 100644
--- a/fpdfsdk/fpdfannot_embeddertest.cpp
+++ b/fpdfsdk/fpdfannot_embeddertest.cpp
@@ -575,6 +575,7 @@
 
     // Check that this annotation has one path object and retrieve it.
     EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get()));
+    ASSERT_EQ(32, FPDFPage_CountObjects(page));
     FPDF_PAGEOBJECT path = FPDFAnnot_GetObject(annot.get(), 1);
     EXPECT_FALSE(path);
     path = FPDFAnnot_GetObject(annot.get(), 0);
@@ -601,6 +602,10 @@
     EXPECT_TRUE(FPDFAnnot_AppendObject(annot.get(), dot));
     EXPECT_EQ(2, FPDFAnnot_GetObjectCount(annot.get()));
 
+    // The object is in the annontation, not in the page, so the page object
+    // array should not change.
+    ASSERT_EQ(32, FPDFPage_CountObjects(page));
+
     // Check that the page with an annotation with two paths renders correctly.
     {
       std::unique_ptr<void, FPDFBitmapDeleter> bitmap =
@@ -611,6 +616,7 @@
     // Delete the newly added path object.
     EXPECT_TRUE(FPDFAnnot_RemoveObject(annot.get(), 1));
     EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get()));
+    ASSERT_EQ(32, FPDFPage_CountObjects(page));
   }
 
   // Check that the page renders the same as before.
diff --git a/fpdfsdk/fpdfedit_embeddertest.cpp b/fpdfsdk/fpdfedit_embeddertest.cpp
index 3fc6cae..ec403c7 100644
--- a/fpdfsdk/fpdfedit_embeddertest.cpp
+++ b/fpdfsdk/fpdfedit_embeddertest.cpp
@@ -381,6 +381,96 @@
   VerifySavedDocument(612, 792, kLastMD5);
 }
 
+TEST_F(FPDFEditEmbeddertest, RemovePageObject) {
+  // Load document with some text.
+  EXPECT_TRUE(OpenDocument("hello_world.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+// Show how the original file looks like.
+#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_
+  const char kOriginalMD5[] = "b90475ca64d1348c3bf5e2b77ad9187a";
+#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_
+  const char kOriginalMD5[] = "e5a6fa28298db07484cd922f3e210c88";
+#else
+  const char kOriginalMD5[] = "2baa4c0e1758deba1b9c908e1fbd04ed";
+#endif
+  {
+    std::unique_ptr<void, FPDFBitmapDeleter> page_bitmap =
+        RenderPageWithFlags(page, nullptr, 0);
+    CompareBitmap(page_bitmap.get(), 200, 200, kOriginalMD5);
+  }
+
+  // Get the "Hello, world!" text object and remove it.
+  ASSERT_EQ(2, FPDFPage_CountObjects(page));
+  FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(page, 0);
+  ASSERT_TRUE(page_object);
+  EXPECT_TRUE(FPDFPage_RemoveObject(page, page_object));
+
+// Verify the "Hello, world!" text is gone.
+#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_
+  const char kRemovedMD5[] = "af760c4702467cb1492a57fb8215efaa";
+#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_
+  const char kRemovedMD5[] = "72be917349bf7004a5c39661fe1fc433";
+#else
+  const char kRemovedMD5[] = "b76df015fe88009c3c342395df96abf1";
+#endif
+  {
+    std::unique_ptr<void, FPDFBitmapDeleter> page_bitmap =
+        RenderPageWithFlags(page, nullptr, 0);
+    CompareBitmap(page_bitmap.get(), 200, 200, kRemovedMD5);
+  }
+  ASSERT_EQ(1, FPDFPage_CountObjects(page));
+
+  UnloadPage(page);
+  FPDFPageObj_Destroy(page_object);
+}
+
+TEST_F(FPDFEditEmbeddertest, AddAndRemovePaths) {
+  // Start with a blank page.
+  FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792);
+  ASSERT_TRUE(page);
+
+  // Render the blank page and verify it's a blank bitmap.
+  const char kBlankMD5[] = "1940568c9ba33bac5d0b1ee9558c76b3";
+  {
+    std::unique_ptr<void, FPDFBitmapDeleter> page_bitmap =
+        RenderPageWithFlags(page, nullptr, 0);
+    CompareBitmap(page_bitmap.get(), 612, 792, kBlankMD5);
+  }
+  ASSERT_EQ(0, FPDFPage_CountObjects(page));
+
+  // Add a red rectangle.
+  FPDF_PAGEOBJECT red_rect = FPDFPageObj_CreateNewRect(10, 10, 20, 20);
+  ASSERT_TRUE(red_rect);
+  EXPECT_TRUE(FPDFPath_SetFillColor(red_rect, 255, 0, 0, 255));
+  EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect, FPDF_FILLMODE_ALTERNATE, 0));
+  FPDFPage_InsertObject(page, red_rect);
+  const char kRedRectangleMD5[] = "66d02eaa6181e2c069ce2ea99beda497";
+  {
+    std::unique_ptr<void, FPDFBitmapDeleter> page_bitmap =
+        RenderPageWithFlags(page, nullptr, 0);
+    CompareBitmap(page_bitmap.get(), 612, 792, kRedRectangleMD5);
+  }
+  EXPECT_EQ(1, FPDFPage_CountObjects(page));
+
+  // Remove rectangle and verify it does not render anymore and the bitmap is
+  // back to a blank one.
+  EXPECT_TRUE(FPDFPage_RemoveObject(page, red_rect));
+  {
+    std::unique_ptr<void, FPDFBitmapDeleter> page_bitmap =
+        RenderPageWithFlags(page, nullptr, 0);
+    CompareBitmap(page_bitmap.get(), 612, 792, kBlankMD5);
+  }
+  EXPECT_EQ(0, FPDFPage_CountObjects(page));
+
+  // Trying to remove an object not in the page should return false.
+  EXPECT_FALSE(FPDFPage_RemoveObject(page, red_rect));
+
+  FPDF_ClosePage(page);
+  FPDFPageObj_Destroy(red_rect);
+}
+
 TEST_F(FPDFEditEmbeddertest, PathsPoints) {
   CreateNewDocument();
   FPDF_PAGEOBJECT img = FPDFPageObj_NewImageObj(document_);
diff --git a/fpdfsdk/fpdfeditpage.cpp b/fpdfsdk/fpdfeditpage.cpp
index ca2cf3f..8006133 100644
--- a/fpdfsdk/fpdfeditpage.cpp
+++ b/fpdfsdk/fpdfeditpage.cpp
@@ -174,10 +174,25 @@
   if (!IsPageObject(pPage))
     return;
   pPageObj->SetDirty(true);
+
+  // TODO(hnakashima): Move into CPDF_Page.
   pPage->GetPageObjectList()->push_back(std::move(pPageObjHolder));
   CalcBoundingBox(pPageObj);
 }
 
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPage_RemoveObject(FPDF_PAGE page, FPDF_PAGEOBJECT page_obj) {
+  CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_obj);
+  if (!pPageObj)
+    return false;
+
+  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
+  if (!IsPageObject(pPage))
+    return false;
+
+  return pPage->RemoveObject(pPageObj);
+}
+
 FPDF_EXPORT int FPDF_CALLCONV FPDFPage_CountObject(FPDF_PAGE page) {
   return FPDFPage_CountObjects(page);
 }
@@ -186,6 +201,8 @@
   CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
   if (!IsPageObject(pPage))
     return -1;
+
+  // TODO(hnakashima): Move into CPDF_Page.
   return pdfium::CollectionSize<int>(*pPage->GetPageObjectList());
 }
 
@@ -194,6 +211,8 @@
   CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
   if (!IsPageObject(pPage))
     return nullptr;
+
+  // TODO(hnakashima): Move into CPDF_Page.
   return pPage->GetPageObjectList()->GetPageObjectByIndex(index);
 }
 
diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c
index 193050e..2a0b230 100644
--- a/fpdfsdk/fpdfview_c_api_test.c
+++ b/fpdfsdk/fpdfview_c_api_test.c
@@ -129,6 +129,7 @@
     CHK(FPDFPage_GetRotation);
     CHK(FPDFPage_SetRotation);
     CHK(FPDFPage_InsertObject);
+    CHK(FPDFPage_RemoveObject);
     CHK(FPDFPage_CountObject);
     CHK(FPDFPage_CountObjects);
     CHK(FPDFPage_GetObject);
diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h
index 54735a3..1e84e8f 100644
--- a/public/fpdf_edit.h
+++ b/public/fpdf_edit.h
@@ -148,6 +148,19 @@
 FPDF_EXPORT void FPDF_CALLCONV FPDFPage_InsertObject(FPDF_PAGE page,
                                                      FPDF_PAGEOBJECT page_obj);
 
+// Experimental API.
+// Remove |page_obj| from |page|.
+//
+//   page     - handle to a page
+//   page_obj - handle to a page object to be removed.
+//
+// Returns TRUE on success.
+//
+// Ownership is transferred to the caller. Call FPDFPageObj_Destroy() to free
+// it.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPage_RemoveObject(FPDF_PAGE page, FPDF_PAGEOBJECT page_obj);
+
 // Get number of page objects inside |page|.
 //
 //   page - handle to a page.