Add test for dropped clipping paths when saving dirty text

When saving a document with text that has been changed (dirty), the
text object's stream is regenerated and the clipping paths are dropped.
Add a test PDF and an embedder test proving the behavior.

Bug: pdfium:1558
Change-Id: Ida49ac7abcc4ef7994095d2a656ca53cc60c0a48
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/71670
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp
index 10f950e..c2e7853 100644
--- a/fpdfsdk/fpdf_edit_embeddertest.cpp
+++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -658,6 +658,83 @@
   CloseSavedDocument();
 }
 
+// TODO(crbug.com/pdfium/1558): Clipping paths should be retained after saving
+// changed text.
+TEST_F(FPDFEditEmbedderTest, SetTextKeepClippingPath) {
+  // Load document with some text, with parts clipped.
+  ASSERT_TRUE(OpenDocument("bug_1558.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  static constexpr char kOriginalChecksum[] =
+#if defined(OS_WIN)
+      "220bf2086398fc46ac094952b244c8d9";
+#elif defined(OS_MACOSX)
+      "53cbaad93551ef2ccc27ddd63f2ca2b3";
+#else
+      "ba1936fa8ca1e8cca108da76ff3500a6";
+#endif
+  {
+    // When opened before any editing and saving, the clipping path is rendered.
+    ScopedFPDFBitmap original_bitmap = RenderPage(page);
+    CompareBitmap(original_bitmap.get(), 200, 200, kOriginalChecksum);
+  }
+
+  // "Change" the text in the objects to their current values to force them to
+  // regenerate when saving.
+  {
+    ScopedFPDFTextPage text_page(FPDFText_LoadPage(page));
+    ASSERT_TRUE(text_page);
+    const int obj_count = FPDFPage_CountObjects(page);
+    ASSERT_EQ(2, obj_count);
+    for (int i = 0; i < obj_count; ++i) {
+      FPDF_PAGEOBJECT text_obj = FPDFPage_GetObject(page, i);
+      ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_obj));
+      unsigned long size =
+          FPDFTextObj_GetText(text_obj, text_page.get(),
+                              /*buffer=*/nullptr, /*length=*/0);
+      ASSERT_GT(size, 0u);
+      std::vector<FPDF_WCHAR> buffer = GetFPDFWideStringBuffer(size);
+      ASSERT_EQ(size, FPDFTextObj_GetText(text_obj, text_page.get(),
+                                          buffer.data(), size));
+      EXPECT_TRUE(FPDFText_SetText(text_obj, buffer.data()));
+    }
+  }
+
+  {
+    // After editing but before saving, the clipping path is retained.
+    ScopedFPDFBitmap edited_bitmap = RenderPage(page);
+    CompareBitmap(edited_bitmap.get(), 200, 200, kOriginalChecksum);
+  }
+
+  // Save the file.
+  EXPECT_TRUE(FPDFPage_GenerateContent(page));
+  EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+  UnloadPage(page);
+
+  // Open the saved copy and render it.
+  ASSERT_TRUE(OpenSavedDocument());
+  FPDF_PAGE saved_page = LoadSavedPage(0);
+  ASSERT_TRUE(saved_page);
+
+  {
+    static constexpr char kChangedChecksum[] =
+#if defined(OS_WIN)
+        "da44e0c040ed56dbb60cf44ef033757b";
+#elif defined(OS_MACOSX)
+        "0bfd2dab51dd588a7f77d531582078cd";
+#else
+        "e63e78fcbcfba4b64f2b843fdf2cc3e8";
+#endif
+    // The checksum of |saved_bitmap| should be |kOriginalChecksum|.
+    ScopedFPDFBitmap saved_bitmap = RenderSavedPage(saved_page);
+    CompareBitmap(saved_bitmap.get(), 200, 200, kChangedChecksum);
+  }
+
+  CloseSavedPage(saved_page);
+  CloseSavedDocument();
+}
+
 TEST_F(FPDFEditEmbedderTest, RemovePageObject) {
   // Load document with some text.
   EXPECT_TRUE(OpenDocument("hello_world.pdf"));
diff --git a/testing/resources/bug_1558.in b/testing/resources/bug_1558.in
new file mode 100644
index 0000000..439038c
--- /dev/null
+++ b/testing/resources/bug_1558.in
@@ -0,0 +1,54 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 200]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+  /Contents 5 0 R
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+{{object 5 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+12 40 100 30 re
+W
+n
+0 g
+BT
+20 100 Td
+/F1 12 Tf
+(Clipped Text) Tj
+0 -50 Td
+/F1 12 Tf
+(Unclipped Text) Tj
+ET
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_1558.pdf b/testing/resources/bug_1558.pdf
new file mode 100644
index 0000000..2feec46
--- /dev/null
+++ b/testing/resources/bug_1558.pdf
@@ -0,0 +1,66 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 200]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <<
+      /F1 4 0 R
+    >>
+  >>
+  /Contents 5 0 R
+>>
+endobj
+4 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Times-Roman
+>>
+endobj
+5 0 obj <<
+  /Length 111
+>>
+stream
+q
+12 40 100 30 re
+W
+n
+0 g
+BT
+20 100 Td
+/F1 12 Tf
+(Clipped Text) Tj
+0 -50 Td
+/F1 12 Tf
+(Unclipped Text) Tj
+ET
+Q
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000283 00000 n 
+0000000361 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+524
+%%EOF