Demonstrate PDF saving bug with a "leaky" CTM

Add test cases to show the correct behavior with rectangles.pdf, and the
incorrect behavior with rectangles_with_leaky_ctm.pdf. Here, the test
cases draws a triangle into the PDF and saves a new copy, but the
current PDF saving code does not properly account for the CTM.

Along the way, consolidate expected rectangle PDF dimension constants.

Bug: pdfium:2142
Change-Id: I7826a25c7310db6c2f880df722d82ef0365248f6
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/117334
Reviewed-by: Thomas Sepez <tsepez@google.com>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/fpdfsdk/fpdf_editpath_embeddertest.cpp b/fpdfsdk/fpdf_editpath_embeddertest.cpp
index 110b7c3..df2bd57 100644
--- a/fpdfsdk/fpdf_editpath_embeddertest.cpp
+++ b/fpdfsdk/fpdf_editpath_embeddertest.cpp
@@ -4,6 +4,7 @@
 
 #include "core/fxcrt/check.h"
 #include "core/fxcrt/fx_system.h"
+#include "core/fxge/cfx_defaultrenderdevice.h"
 #include "public/fpdf_edit.h"
 #include "testing/embedder_test.h"
 #include "testing/embedder_test_constants.h"
@@ -11,7 +12,30 @@
 
 using pdfium::RectanglesChecksum;
 
-class FPDFEditPathEmbedderTest : public EmbedderTest {};
+using FPDFEditPathEmbedderTest = EmbedderTest;
+
+namespace {
+
+constexpr int kExpectedRectangleWidth = 200;
+constexpr int kExpectedRectangleHeight = 300;
+
+const char* RectanglesAndTriangleChecksum() {
+  return CFX_DefaultRenderDevice::UseSkiaRenderer()
+             ? "89b85ca2749a98320518531cf365b010"
+             : "8bb78ca28f1e0ab9d36c0745ae0f58bb";
+}
+
+ScopedFPDFPageObject CreateBlackTriangle() {
+  ScopedFPDFPageObject path(FPDFPageObj_CreateNewPath(100, 50));
+  EXPECT_TRUE(FPDFPageObj_SetFillColor(path.get(), 0, 0, 0, 255));
+  EXPECT_TRUE(FPDFPath_SetDrawMode(path.get(), FPDF_FILLMODE_ALTERNATE, 0));
+  EXPECT_TRUE(FPDFPath_LineTo(path.get(), 100, 75));
+  EXPECT_TRUE(FPDFPath_LineTo(path.get(), 75, 75));
+  EXPECT_TRUE(FPDFPath_Close(path.get()));
+  return path;
+}
+
+}  // namespace
 
 TEST_F(FPDFEditPathEmbedderTest, VerifyCorrectColoursReturned) {
   constexpr int kObjectCount = 256;
@@ -67,17 +91,14 @@
 }
 
 TEST_F(FPDFEditPathEmbedderTest, GetAndSetMatrixForPath) {
-  constexpr int kExpectedWidth = 200;
-  constexpr int kExpectedHeight = 300;
-
   OpenDocument("rectangles_double_flipped.pdf");
   FPDF_PAGE page = LoadPage(0);
   ASSERT_TRUE(page);
 
   {
     ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
-    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
-                  RectanglesChecksum());
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesChecksum());
   }
 
   FPDF_PAGEOBJECT path = FPDFPage_GetObject(page, 0);
@@ -96,8 +117,8 @@
   ASSERT_TRUE(FPDFPageObj_SetMatrix(path, &matrix));
   {
     ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
-    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
-                  RectanglesChecksum());
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesChecksum());
   }
 
   ASSERT_TRUE(FPDFPage_GenerateContent(page));
@@ -105,27 +126,25 @@
 
   {
     ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
-    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
-                  RectanglesChecksum());
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesChecksum());
   }
 
   UnloadPage(page);
 
-  VerifySavedDocument(kExpectedWidth, kExpectedHeight, RectanglesChecksum());
+  VerifySavedDocument(kExpectedRectangleWidth, kExpectedRectangleHeight,
+                      RectanglesChecksum());
 }
 
 TEST_F(FPDFEditPathEmbedderTest, GetAndSetMatrixForFormWithPath) {
-  constexpr int kExpectedWidth = 200;
-  constexpr int kExpectedHeight = 300;
-
   OpenDocument("form_object_with_path.pdf");
   FPDF_PAGE page = LoadPage(0);
   ASSERT_TRUE(page);
 
   {
     ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
-    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
-                  RectanglesChecksum());
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesChecksum());
   }
 
   FPDF_PAGEOBJECT form = FPDFPage_GetObject(page, 0);
@@ -144,8 +163,8 @@
   ASSERT_TRUE(FPDFPageObj_SetMatrix(form, &matrix));
   {
     ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
-    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
-                  RectanglesChecksum());
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesChecksum());
   }
 
   FPDF_PAGEOBJECT path = FPDFFormObj_GetObject(form, 0);
@@ -163,8 +182,8 @@
   ASSERT_TRUE(FPDFPageObj_SetMatrix(path, &matrix));
   {
     ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
-    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
-                  RectanglesChecksum());
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesChecksum());
   }
 
   ASSERT_TRUE(FPDFPage_GenerateContent(page));
@@ -172,11 +191,86 @@
 
   {
     ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
-    CompareBitmap(bitmap.get(), kExpectedWidth, kExpectedHeight,
-                  RectanglesChecksum());
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesChecksum());
   }
 
   UnloadPage(page);
 
-  VerifySavedDocument(kExpectedWidth, kExpectedHeight, RectanglesChecksum());
+  VerifySavedDocument(kExpectedRectangleWidth, kExpectedRectangleHeight,
+                      RectanglesChecksum());
+}
+
+TEST_F(FPDFEditPathEmbedderTest, AddPathToRectangles) {
+  OpenDocument("rectangles.pdf");
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesChecksum());
+  }
+
+  ScopedFPDFPageObject path = CreateBlackTriangle();
+  ASSERT_TRUE(path);
+  FPDFPage_InsertObject(page, path.release());
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesAndTriangleChecksum());
+  }
+
+  EXPECT_TRUE(FPDFPage_GenerateContent(page));
+  EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesAndTriangleChecksum());
+  }
+
+  UnloadPage(page);
+
+  VerifySavedDocument(kExpectedRectangleWidth, kExpectedRectangleHeight,
+                      RectanglesAndTriangleChecksum());
+}
+
+TEST_F(FPDFEditPathEmbedderTest, AddPathToRectanglesWithLeakyCTM) {
+  OpenDocument("rectangles_with_leaky_ctm.pdf");
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesChecksum());
+  }
+
+  ScopedFPDFPageObject path = CreateBlackTriangle();
+  ASSERT_TRUE(path);
+  FPDFPage_InsertObject(page, path.release());
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesAndTriangleChecksum());
+  }
+
+  EXPECT_TRUE(FPDFPage_GenerateContent(page));
+  EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+    CompareBitmap(bitmap.get(), kExpectedRectangleWidth,
+                  kExpectedRectangleHeight, RectanglesAndTriangleChecksum());
+  }
+
+  UnloadPage(page);
+
+  // TODO(crbug.com/pdfium/2142): This is the wrong checksum. It should be
+  // RectanglesAndTriangleChecksum().
+  VerifySavedDocument(kExpectedRectangleWidth, kExpectedRectangleHeight,
+                      RectanglesChecksum());
 }
diff --git a/testing/resources/rectangles_with_leaky_ctm.in b/testing/resources/rectangles_with_leaky_ctm.in
new file mode 100644
index 0000000..6f7f109
--- /dev/null
+++ b/testing/resources/rectangles_with_leaky_ctm.in
@@ -0,0 +1,45 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+2 0 0 -3 -50 100 cm
+q
+0.5 0 0 -0.3333333 25 33.3333333 cm % Invert the transform 2 lines above.
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/rectangles_with_leaky_ctm.pdf b/testing/resources/rectangles_with_leaky_ctm.pdf
new file mode 100644
index 0000000..5345ef3
--- /dev/null
+++ b/testing/resources/rectangles_with_leaky_ctm.pdf
@@ -0,0 +1,56 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 300]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 282
+>>
+stream
+2 0 0 -3 -50 100 cm
+q
+0.5 0 0 -0.3333333 25 33.3333333 cm % Invert the transform 2 lines above.
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+560
+%%EOF