Generate clipping path when processing the graphics of page objects

When reprocessing the graphics of an edited/dirty page object, the
clipping path is ignored. Keep those clipping paths instead.

Bug: pdfium:1558
Change-Id: Idead22cbf7796a8971e3af164464d8c80956d1e7
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/71713
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
index 573b40c..b07622a 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
@@ -404,6 +404,8 @@
 // "rg" sets the fill color, "RG" sets the stroke color (using DefaultRGB)
 // "w" sets the stroke line width.
 // "ca" sets the fill alpha, "CA" sets the stroke alpha.
+// "W" and "W*" modify the clipping path using the nonzero winding rule and
+// even-odd rules, respectively.
 // "q" saves the graphics state, so that the settings can later be reversed
 void CPDF_PageContentGenerator::ProcessGraphics(std::ostringstream* buf,
                                                 CPDF_PageObject* pPageObj) {
@@ -428,6 +430,29 @@
   if (lineJoin != CFX_GraphStateData::LineJoinMiter)
     *buf << static_cast<int>(lineJoin) << " j ";
 
+  const CPDF_ClipPath& clip_path = pPageObj->m_ClipPath;
+  if (clip_path.HasRef()) {
+    for (size_t i = 0; i < clip_path.GetPathCount(); ++i) {
+      CPDF_Path path = clip_path.GetPath(i);
+      ProcessPathPoints(buf, &path);
+      switch (clip_path.GetClipType(i)) {
+        case CFX_FillRenderOptions::FillType::kWinding:
+          *buf << " W ";
+          break;
+        case CFX_FillRenderOptions::FillType::kEvenOdd:
+          *buf << " W* ";
+          break;
+        case CFX_FillRenderOptions::FillType::kNoFill:
+          NOTREACHED();
+          break;
+      }
+
+      // Use a no-op path-painting operator to terminate the path without
+      // causing any marks to be placed on the page.
+      *buf << "n 0 g ";
+    }
+  }
+
   GraphicsData graphD;
   graphD.fillAlpha = pPageObj->m_GeneralState.GetFillAlpha();
   graphD.strokeAlpha = pPageObj->m_GeneralState.GetStrokeAlpha();
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
index c3fe7f0..5e145b2 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
@@ -331,6 +331,18 @@
     pTextObj->m_TextState.SetFontSize(15.5f);
     pTextObj->SetText("I am indirect");
     pTextObj->SetTextRenderMode(TextRenderingMode::MODE_FILL_CLIP);
+
+    // Add a clipping path.
+    auto pPath = std::make_unique<CPDF_Path>();
+    pPath->AppendPoint(CFX_PointF(0, 0), FXPT_TYPE::MoveTo);
+    pPath->AppendPoint(CFX_PointF(5, 0), FXPT_TYPE::LineTo);
+    pPath->AppendPoint(CFX_PointF(5, 4), FXPT_TYPE::LineTo);
+    pPath->AppendPointAndClose(CFX_PointF(0, 4), FXPT_TYPE::LineTo);
+    pTextObj->m_ClipPath.Emplace();
+    pTextObj->m_ClipPath.AppendPath(*pPath,
+                                    CFX_FillRenderOptions::FillType::kEvenOdd,
+                                    /*bAutoMerge=*/false);
+
     TestProcessText(&generator, &buf, pTextObj.get());
   }
 
@@ -342,7 +354,7 @@
   ByteString lastString =
       textString.Last(textString.GetLength() - firstResourceAt.value());
   // q and Q must be outside the BT .. ET operations
-  ByteString compareString1 = "q BT 1 0 0 1 0 0 Tm /";
+  ByteString compareString1 = "q 0 0 5 4 re W* n 0 g BT 1 0 0 1 0 0 Tm /";
   ByteString compareString2 =
       " 15.5 Tf 4 Tr <4920616D20696E646972656374> Tj ET Q\n";
   EXPECT_LT(compareString1.GetLength() + compareString2.GetLength(),
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp
index 0cdcb9f..6371efc 100644
--- a/fpdfsdk/fpdf_edit_embeddertest.cpp
+++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -658,8 +658,6 @@
   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"));
@@ -718,17 +716,8 @@
   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);
+    CompareBitmap(saved_bitmap.get(), 200, 200, kOriginalChecksum);
   }
 
   CloseSavedPage(saved_page);